One place for hosting & domains

      Disponer en contenedor una aplicación de Node.js para el desarrollo con Docker Compose


      Introducción

      Si desarrolla activamente una aplicación, usando Docker, puede simplificar su flujo de trabajo y el proceso de implementación de su aplicación para producción. El trabajo con contenedores en tareas de desarrollo tiene los siguientes beneficios:

      • Los entornos son uniformes, lo cual significa que puede elegir los lenguajes y las dependencias que desee para su proyecto sin tener que preocuparse por posibles conflictos del sistema.
      • Los entornos están aislados. Esto facilita la resolución de problemas y la admisión de nuevos miembros del equipo.
      • Los entornos son portátiles; esto permite empaquetar y compartir su código con otros.

      A través de este tutorial, verá la forma de configurar un entorno de desarrollo para una aplicación de Node.js usando Docker. Creará dos contenedores: uno para la aplicación de Node y otro para la base de datos de MongoDB, con Docker Compose. Debido a que esta aplicación funciona con Node y MongoDB, nuestra configuración realizará lo siguiente:

      • Sincronizar el código de la aplicación del host con el código del contenedor para facilitar los cambios durante el desarrollo.
      • Garantizar que los cambios al código de la aplicación funcionen sin un reinicio.
      • Crear una base de datos protegida por nombre de usuario y contraseña para los datos de la aplicación.
      • Hacer que los datos sean persistentes.

      Al finalizar este tutorial, contará con una aplicación de información sobre tiburones en funcionamiento en contenedores de Docker:

      Completar la colección de tiburones

      Requisitos previos

      Para seguir este tutorial, necesitará lo siguiente:

      Paso 1: Clonar el proyecto y modificar las dependencias

      El primer paso para crear esta configuración será clonar el código del proyecto y modificar su archivo package.json, que incluye las dependencias del proyecto. Añadiremos nodemon a devDependencies del proyectoy especificaremos que lo utilizaremos durante el desarrollo. Ejecutar la aplicación con nodemon garantiza que se reiniciará automáticamente cuando realice cambios en su código.

      Primero, clone el repositorio nodejs-mongo-mongoose desde la cuenta de GitHub de la comunidad de DigitalOcean. Este repositorio incluye el código de la configuración descrita en el artículo Cómo integrar MongoDB con su aplicación de Node, en el que se explica la manera de integrar una base de datos de MongoDB con una aplicación de Node existente usando Mongoose.

      Clone el repositorio en un directorio llamado node_project:

      • git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project

      Diríjase al directorio node_project:

      Abra el archivo package.json del proyecto usando nano o su editor favorito:

      Debajo de las dependencias del proyecto y encima de la llave de cierre, cree un nuevo objeto devDependencies que incluya nodemon:

      ~/node_project/package.json

      ...
      "dependencies": {
          "ejs": "^2.6.1",
          "express": "^4.16.4",
          "mongoose": "^5.4.10"
        },
        "devDependencies": {
          "nodemon": "^1.18.10"
        }    
      }
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      Una vez que se implemente el código del proyecto y se modifiquen sus dependencias, podrá proceder a refactorizar el código para un flujo de trabajo en contenedor.

      Paso 2: Configurar su aplicación para que funcione con contenedores

      Modificar nuestra aplicación para un flujo de trabajo en contenedores implica hacer que nuestro código sea más modular. Los contenedores ofrecen portabilidad entre entornos, y nuestro código debería reflejar esto manteniendo un nivel de disociación lo más alto posible respecto del sistema operativo subyacente. A fin de lograr esto, refactorizaremos nuestro código para hacer un mayor uso de la propiedad process.env de Node, que muestra un objeto con información sobre su entorno de usuario en el tiempo de ejecución. Podemos usar este objeto en nuestro código para asignar de forma dinámica información de la configuración en el tiempo de ejecución con variables de entorno.

      Comenzaremos con apps.js, nuestro punto de entrada principal para la aplicación. Abra el archivo:

      Dentro, verá una definición para una constante port, además de una función listen que utiliza esta constante para especificar el puerto en el que la aplicación escuchará.

      ~/home/node_project/app.js

      ...
      const port = 8080;
      ...
      app.listen(port, function () {
        console.log('Example app listening on port 8080!');
      });
      

      Redefiniremos la constante port para permitir la asignación dinámica en el tiempo de ejecución usando el objeto process.env. Realice los siguientes cambios en la definición de la constante y la función listen:

      ~/home/node_project/app.js

      ...
      const port = process.env.PORT || 8080;
      ...
      app.listen(port, function () {
        console.log(`Example app listening on ${port}!`);
      });
      

      Con nuestra nueva definición de la constante, port se asigna de forma dinámica usando el valor transmitido en el tiempo de ejecución o 8080. De modo similar, reescribimos la función listen para que use un literal de plantilla que interpolará el valor del puerto al escuchar conexiones. Debido a que asignaremos nuestros puertos en otra parte, estas revisiones evitarán que debamos revisar continuamente este archivo a medida que nuestro entorno cambie.

      Una vez que finalice la edición, guarde y cierre el archivo.

      A continuación, modificaremos la información de conexión de nuestra base de datos para eliminar cualquier credencial de configuración. Abra el archivo db.js, que contiene esta información:

      Actualmente, el archivo hace lo siguiente:

      • Importa Mongoose, el asignador de objeto a documento (ODM) que usaremos para crear esquemas y modelos para los datos de nuestra aplicación.
      • Establece las credenciales de la base de datos como constantes, incluidos el nombre de usuario y la contraseña.
      • Establece conexión con la base de datos usando el método mongoose.connect.

      Para obtener más información sobre el archivo, consulte el paso 3 de Cómo integrar MongoDB con su aplicación de Node.

      Nuestro primer paso para modificar el archivo será redefinir las constantes que incluyan información confidencial. Actualmente, estas constantes tendrán este aspecto:

      ~/node_project/db.js

      ...
      const MONGO_USERNAME = 'sammy';
      const MONGO_PASSWORD = 'your_password';
      const MONGO_HOSTNAME = '127.0.0.1';
      const MONGO_PORT = '27017';
      const MONGO_DB = 'sharkinfo';
      ...
      

      En vez de realizar una codificación rígida de esta información, puede usar el objeto process.env a fin de capturar los valores del tiempo de ejecución para estas constantes: Modifique el bloque para que tenga este aspecto:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      ...
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      En este punto, habrá modificado db.jspara que funcione con las variables de entorno de su aplicación, pero aún necesita una forma de pasar estas variables a su aplicación. Crearemos un archivo .env con valores que pueda pasar a su aplicación en el tiempo de ejecución.

      Abra el archivo:

      Este archivo incluirá la información que eliminó de db.js: el nombre de usuario y la contraseña para la base de su aplicación, además del ajuste del puerto y el nombre de la base de datos. Recuerde actualizar el nombre de usuario, la contraseña y el nombre de la base de datos que se muestran aquí con su propia información:

      ~/node_project/.env

      MONGO_USERNAME=sammy
      MONGO_PASSWORD=your_password
      MONGO_PORT=27017
      MONGO_DB=sharkinfo
      

      Tenga en cuenta que eliminamos el ajuste de host que originalmente aparecía en db.js. Ahora definiremos nuestro host a nivel del archivo de Docker Compose, junto con información adicional sobre nuestros servicios y contenedores.

      Guarde y cierre este archivo cuando concluya la edición.

      Debido a que su archivo .env contiene información confidencial, le convendrá asegurarse de que se incluya en los archivos .dockerignore y .gitignore de su proyecto para que no realice copias al control de su versión o a los contenedores.

      Abra su archivo .dockerignore:

      Añada la siguiente línea a la parte inferior del archivo:

      ~/node_project/.dockerignore

      ...
      .gitignore
      .env
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      El archivo .gitignore de este repositorio ya incluye .env, pero verifique que esté allí:

      ~~/node_project/.gitignore

      ...
      .env
      ...
      

      En este punto, habrá extraído correctamente la información confidencial del código de su proyecto y tomado medidas para controlar la forma y la ubicación en que se copia esta información. Ahora, podrá aportar más solidez al código de conexión de su base de datos a fin de optimizarlo para un flujo de trabajo en contenedor.

      Paso 3: Modificar ajustes de conexión de la base de datos

      Nuestro siguiente paso será hacer que el método de conexión de nuestra base de datos sea más sólido añadiendo código que gestione los casos en los que nuestra aplicación no se conecte con nuestra base de datos. Sumar este nivel de resistencia al código de su aplicación es una práctica recomendada cuando se trabaja con contenedores usando Compose.

      Abra db.js para editarlo:

      Verá el código que añadimos antes, junto con la constante url para la URI de conexión de Mongo y el método connect de Mongoose:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, {useNewUrlParser: true});
      

      Actualmente, nuestro método connect acepta una opción que indica a Mongoose que utilice el nuevo analizador de URL de Mongo. Añadiremos algunas opciones más a este método para definir parámetros para intentos de reconexión. Podemos hacer esto creando una constante options que incluya la información pertinente, además de usar la opción del nuevo analizador de URL. En sus constantes de Mongo, añada la siguiente definición para una constante options:

      ~/node_project/db.js

      ...
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      ...
      

      La opción reconnectTries indica a Mongoose que siga intentando establecer conexión indefinidamente, mientras que reconnectInterval define el período entre los intentos de conexión en milisegundos. connectTimeoutMS define 10 segundos como el período que el controlador de Mongo esperará antes de que falle el intento de conexión.

      Ahora podemos usar la nueva constante options en el método connect de Mongoose para ajustar nuestra configuración de conexión de Mongoose. También añadiremos una promesa para manejar los posibles errores de conexión.

      En este momento, el método connect de Mongoose tiene este aspecto:

      ~/node_project/db.js

      ...
      mongoose.connect(url, {useNewUrlParser: true});
      

      Elimine el método connect existente y sustitúyalo por el siguiente código, que incluye la constante options y una promesa:

      ~/node_project/db.js

      ...
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      En caso de que la conexión se realice correctamente, nuestra función registrará un mensaje correspondiente; de lo contrario, aplicará catch al error y lo registrará para que podamos resolverlo.

      El archivo terminado tendrá este aspecto:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      
      const {
        MONGO_USERNAME,
        MONGO_PASSWORD,
        MONGO_HOSTNAME,
        MONGO_PORT,
        MONGO_DB
      } = process.env;
      
      const options = {
        useNewUrlParser: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 500,
        connectTimeoutMS: 10000,
      };
      
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, options).then( function() {
        console.log('MongoDB is connected');
      })
        .catch( function(err) {
        console.log(err);
      });
      

      Guarde y cierre el archivo cuando concluya la edición.

      Con esto, habrá añadido resistencia al código de su aplicación para gestionar los casos en los cuales fuera posible que esta no pudiera establecer conexión con su base de datos. Una vez establecido este código, puede proceder a definir sus servicios con Compose.

      Paso 4: Definir servicios con Docker Compose

      Una vez refactorizado su código, estará listo para escribir el archivo docker-compose.yml con las definiciones de su servicio. Un servicio en Compose es un contenedor en ejecución y las definiciones del servicio, que incluirá en su archivo docker-compose.yml, contienen información sobre cómo se ejecutará cada imagen del contenedor. La herramienta Compose le permite definir varios servicios para crear aplicaciones en diferentes contenedores.

      Antes de definir nuestros servicios, sin embargo, añadiremos una herramienta a nuestro proyecto llamada wait-for para garantizar que nuestra aplicación solo intente establecer conexión con nuestra base de datos una vez que las tareas de inicio de esta última se completen. Esta secuencia de comandos utiliza netcat para determinar, mediante un sondeo, si un host y puerto específicos aceptan conexiones TCP o no. Usarla le permite controlar los intentos que su aplicación realiza para establecer conexión con su base de datos determinando, mediante una prueba, si la base de datos está lista o no para aceptar conexiones.

      Aunque Compose le permite especificar dependencias entre los servicios usando la opción depends_on, esta orden se basa más en el hecho que el contenedor se ejecute o no que en el hecho de que esté preparado. Usar depends_on no será la mejor opción para nuestra configuración, pues queremos que nuestra aplicación se conecte solo cuando las se completen tareas de la base de datos, incluida la de añadir usuario y contraseña a la base de datos de autenticación de admin. Para obtener más información sobre cómo usar wait-for y otras herramientas para controlar la orden de inicio, consulte las recomendaciones pertinentes en la documentación de Compose.

      Abra un archivo llamado wait-for.sh:

      Pegue el siguiente código en el archivo para crear la función de sondeo:

      ~/node_project/app/wait-for.sh

      #!/bin/sh
      
      # original script: https://github.com/eficode/wait-for/blob/master/wait-for
      
      TIMEOUT=15
      QUIET=0
      
      echoerr() {
        if [ "$QUIET" -ne 1 ]; then printf "%sn" "$*" 1>&2; fi
      }
      
      usage() {
        exitcode="$1"
        cat << USAGE >&2
      Usage:
        $cmdname host:port [-t timeout] [-- command args]
        -q | --quiet                        Do not output any status messages
        -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
        -- COMMAND ARGS                     Execute command with args after the test finishes
      USAGE
        exit "$exitcode"
      }
      
      wait_for() {
        for i in `seq $TIMEOUT` ; do
          nc -z "$HOST" "$PORT" > /dev/null 2>&1
      
          result=$?
          if [ $result -eq 0 ] ; then
            if [ $# -gt 0 ] ; then
              exec "$@"
            fi
            exit 0
          fi
          sleep 1
        done
        echo "Operation timed out" >&2
        exit 1
      }
      
      while [ $# -gt 0 ]
      do
        case "$1" in
          *:* )
          HOST=$(printf "%sn" "$1"| cut -d : -f 1)
          PORT=$(printf "%sn" "$1"| cut -d : -f 2)
          shift 1
          ;;
          -q | --quiet)
          QUIET=1
          shift 1
          ;;
          -t)
          TIMEOUT="$2"
          if [ "$TIMEOUT" = "" ]; then break; fi
          shift 2
          ;;
          --timeout=*)
          TIMEOUT="${1#*=}"
          shift 1
          ;;
          --)
          shift
          break
          ;;
          --help)
          usage 0
          ;;
          *)
          echoerr "Unknown argument: $1"
          usage 1
          ;;
        esac
      done
      
      if [ "$HOST" = "" -o "$PORT" = "" ]; then
        echoerr "Error: you need to provide a host and port to test."
        usage 2
      fi
      
      wait_for "$@"
      

      Guarde y cierre el archivo cuando termine de añadir el código.

      Haga que la secuencia de comandos sea ejecutable:

      A continuación, abra el archivo docker-compose.yml:

      Primero defina el servicio de la aplicación nodejs agregando el siguiente código al archivo:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      

      La definición del servicio nodejs incluye las siguientes opciones:

      • build: define las opciones de configuración, incluido el context y dockerfile, que se aplicarán cuando Compose cree la imagen de la aplicación. Si desea utilizar una imagen existente de un registro como Docker Hub, podría utilizar la instrucción image como alternativa, con información sobre su nombre de usuario, repositorio y etiqueta de imagen.
      • context: esto define el contexto de compilación para la compilación de la imagen; en este caso, el directorio del proyecto actual.
      • dockerfile: esto especifica el Dockerfile del directorio actual de su directorio como el archivo que Compose usará para complilar la imagen de la aplicación. Para obtener más información sobre este archivo, consulte Cómo crear una aplicación de Node.js con Docker.
      • image y container_name: aplican nombres a la imagen y al contenedor.
      • restart: define la política de reinicio. El valor predeterminado es no, pero configuramos el contenedor para reiniciarse a menos que se detenga.
      • env_file: indica a Compose que deseamos añadir variables de entorno de un archivo llamado .env, ubicado en nuestro contexto de compilación.
      • environment: esta opción le permite añadir los ajustes de conexión de Mongo que definió en el archivo .env. Tenga en cuenta que no fijaremos NODE_ENV en development, ya que éste es el comportamiento predeterminado de Express si NODE_ENV no se configura. Cuando procedamos con la producción, podrá fijarlo en production para permitir el almacenamiento de vistas en caché y recibir menos mensajes de error confusos. Observe, además, que especificamos el contenedor de la base de datos db como host, como se explicó en el paso 2.
      • ports: asigna el puerto 80 del host al puerto 8080 del contenedor.
      • volumes: incluiremos dos tipos de montajes aquí:
        • El primero es un montaje de enlace, que monta el código de nuestra aplicación del host en el directorio /home/node/app del contenedor. Esto facilitará un desarrollo rápido, ya que cualquier cambio que realice a su código de host se completará de inmediato en el contenedor.
        • El segundo es un volumen con nombre: node_modules. Cuando Docker ejecute la instrucción npm install que se indica en el Dockerfile de la aplicación, npm creará en el contenedor un nuevo directorio node_modules en el que se incluirán los paquetes necesarios para ejecutar la aplicación. El montaje de enlace que acabamos de crear ocultará, sin embargo, este directorio node_modules recién creado. Debido a que node_modules en el host está vacío, el bind asignará un directorio vacío al contenedor, con lo cual se anulará el nuevo directorio node_modules y se evitará el inicio de nuestra aplicación. El volumen llamado node_modules resuelve este problema haciendo que persista el contenido del directorio /home/node/app/node_modules y montándolo en el contenedor, con lo cual se ocultará el enlace.

      Tenga en cuenta lo siguiente cuando utilice este enfoque:

      • Su enlace montará los contenidos del directorio node_modules del contenedor en el host y este directorio será propiedad de root, ya que el volumen nombrado fue creado por Docker.
      • Si dispone de un directorio node_modules preexistente en el host, anulará el directorio node_modules creado en el contenedor. Para configuración que estamos creando en este tutorial, se supone que no dispone de un directorio node_modules preexistente y que no trabajará con npm en su host. Esto se ajusta a un enfoque de doce factores para el desarrollo de la aplicación, que minimiza las dependencias entre los entornos de ejecución.

        • networks: especifica que nuestro servicio de aplicación se unirá a la red app-network, que definiremos al final del archivo.
        • command: esta opción le permite establecer el comando que debería ejecutarse cuando Compose ejecute la imagen. Tenga en cuenta que con esto se anulará la instrucción CMD que establecimos en el Dockerfile de nuestra aplicación. En este caso, ejecutaremos la aplicación usando la secuencia de comandos wait-for que sondeará el servicio db en el puerto 27017 para probar si el servicio de la base de datos está listo o no. Una vez que esta prueba se realice, la secuencia de comandos ejecutará el comando que establecimos, /home/node/app/node_modules/.bin/nodemon app.js, para iniciar la aplicación con nodemon. Esto garantizará que cualquier cambio futuro que realicemos en nuestro código se recargue sin que debamos reiniciar la aplicación.

      A continuación, cree el servicio db agregando el siguiente código debajo de la definición del servicio de la aplicación:

      ~/node_project/docker-compose.yml

      ...
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:  
            - dbdata:/data/db   
          networks:
            - app-network  
      

      Algunos de los ajustes que definimos para el servicio nodejs seguirán siendo los mismos, pero también realizamos los siguientes cambios en las definiciones de image, environment y volumes:

      • image: para crear este servicio, Compose extraerá la imagen de Mongo 4.1.8-xenial de Docker Hub. Fijaremos una versión concreta para evitar posibles conflictos futuros a medida que la imagen de Mongo cambie. Para obtener más información sobre la fijación de la versiones, consulte la documentación de Docker en Prácticas recomendadas de Dockerfile.
      • MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD: la imagen de mongo pone estas variables de entorno a disposición para que pueda modificar la inicialización de la instancia de su base de datos. MONGO_INITDB_ROOT_USERNAME y MONGO_INITDB_ROOT_PASSWORD crean juntos un usuario root en la base de datos de autenticación admin y verifican que la autenticación esté habilitada cuando se inicie el contenedor. Configuramos MONGO_INITDB_ROOT_USERNAME y MONGO_INITDB_ROOT_PASSWORD usando los valores de nuestro archivo .env, que pasamos al servicio db usando la opción env_file. Hacer esto significa que nuestro usuario de la aplicación sammy será un usuario root en la instancia de la base de datos, con acceso a todos los privilegios administrativos y operativos de esa función. Cuando trabaje en producción, le convendrá crear un usuario de aplicación dedicado con privilegios de alcance correspondiente.

        Nota: Tenga en cuenta que estas variables no se aplicarán si inicia el contenedor con un directorio de datos existente implementado.
      • dbdata:/data/db: el volumen llamado dbdata preservará los datos almacenados en /data/db, el directorio de datos predeterminado de Mongo. Esto garantizará que no pierda datos en los casos en los que detenga o elimine contenedores.

      También agregamos el servcio db a la red app-network con la opción networks.

      Como paso final, añada las definiciones de volumen y red al final del archivo:

      ~/node_project/docker-compose.yml

      ...
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      La red de puente definida por el usuario app-network permite la comunicación entre nuestros contenedores, ya que están en el mismo host de demonio de Docker. Esto agiliza el tráfico y la comunicación dentro de la aplicación, ya que abre todos los puertos entre contenedores en la misma red de puente y, al mismo tiempo, no expone ningún puerto al exterior. Por lo tanto, nuestros contenedores db y nodejs pueden comunicarse entre sí y solo debemos exponer el puerto 80 para el acceso de front-end a la aplicación.

      Nuestra clave volumes de nivel nivel superior define dbdata y node_modules de los volúmenes. Cuando Docker crea volúmenes, el contenido de estos se almacena en una parte del sistema de archivos host, /var/ib/docker/volume/, que Docker administra. El contenido de cada volumen se almacena en un directorio en /var/lib/docker/volume/ y se monta en cualquier contenedor que utilice el volumen. De esta forma, los datos de la información sobre tiburones que nuestros usuarios crearán persistirán en el volumen dbdata, incluso si eliminamos y volvemos a crear el contenedor db.

      El archivo docker-compose.yml terminado tendrá este aspecto:

      ~/node_project/docker-compose.yml

      version: '3'
      
      services:
        nodejs:
          build:
            context: .
            dockerfile: Dockerfile
          image: nodejs
          container_name: nodejs
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_USERNAME=$MONGO_USERNAME
            - MONGO_PASSWORD=$MONGO_PASSWORD
            - MONGO_HOSTNAME=db
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
          ports:
            - "80:8080"
          volumes:
            - .:/home/node/app
            - node_modules:/home/node/app/node_modules
          networks:
            - app-network
          command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
      
        db:
          image: mongo:4.1.8-xenial
          container_name: db
          restart: unless-stopped
          env_file: .env
          environment:
            - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
            - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
          volumes:     
            - dbdata:/data/db
          networks:
            - app-network  
      
      networks:
        app-network:
          driver: bridge
      
      volumes:
        dbdata:
        node_modules:  
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      Una vez implementadas las definiciones de su servicio, estará listo para iniciar la aplicación.

      Paso 5: Probar la aplicación

      Una vez implementado su archivo docker-compose.yml, puede crear sus servicios con el comando docker-compose up. También puede comprobar que sus datos persisitirán deteniendo y eliminando sus contenedores con docker-compose down.

      Primero, compile las imágenes del contenedor y cree los servicios ejecutando docker-compose up con el indicador -d, que luego ejecutará los contenedores nodejs y db en segundo plano.

      Verá un resultado que confirmará la creación de sus servicios:

      Output

      ... Creating db ... done Creating nodejs ... done

      También puede obtener información más detallada sobre los procesos de inicio mostrando el resultado del registro de los servicios:

      Si todo se inició de forma correcta, verá algo similar a esto:

      Output

      ... nodejs | [nodemon] starting `node app.js` nodejs | Example app listening on 8080! nodejs | MongoDB is connected ... db | 2019-02-22T17:26:27.329+0000 I ACCESS [conn2] Successfully authenticated as principal sammy on admin

      También puede verificar el estado de sus contenedores con docker-compose ps:

      Verá un resultado que indicará que sus contenedores están en ejecución:

      Output

      Name Command State Ports ---------------------------------------------------------------------- db docker-entrypoint.sh mongod Up 27017/tcp nodejs ./wait-for.sh db:27017 -- ... Up 0.0.0.0:80->8080/tcp

      Una vez que los servicios estén en ejecución, podrá visitar http://your_server_ip en el navegador. Verá una página de aterrizaje similar a esta:

      Página de destino de la aplicación

      Haga clic en el botón Get Shark Info. Verá una página con un formulario de entrada en el que podrá introducir un nombre del tiburón y una descripción del carácter general de este:

      Formulario de Información sobre tiburones

      En el formulario, agregue un tiburón que elija. A los efectos de esta demostración, añadiremos Megalodon Shark en el campo Shark Name y Ancient en el campo Shark Character:

      Formulario de tiburones completado

      Haga clic en el botón Submit. Visualizará una página con la siguiente información sobre tiburones que se le mostrará de nuevo:

      Resultado de tiburones

      Como paso final, podemos hacer una prueba para verificar que los datos que acaba de introducir persistan si elimina el contenedor de su base de datos.

      Cuando regrese a su terminal, escriba el siguiente comando para detener y eliminar sus contenedores y su red:

      Tenga en cuenta que no incluiremos la opción --volumes; por lo tanto, no se eliminará nuestro volumen dbdata.

      El siguiente resultado confirma que se eliminaron sus contenedores y su red:

      Output

      Stopping nodejs ... done Stopping db ... done Removing nodejs ... done Removing db ... done Removing network node_project_app-network

      Vuelva a crear los contenedores:

      Ahora, vuelva al formulario de información de tiburones:

      Formulario de información  sobre tiburones

      Introduzca un nuevo tiburón que elija. Usaremos Whale Shark y Large:

      Introduzca un nuevo tiburón

      Una vez que haga clic en Submit, verá que se agregó el nuevo tiburón a la colección de tiburones de su base de datos sin pérdida de datos que ya introdujo:

      Colección completa de tiburones

      Su aplicación ahora se ejecuta en los contenedores de Docker con la persistencia de datos y la sincronización de códigos habilitadas.

      Conclusión

      Siguiendo este tutorial, creó una configuración de desarrollo para su aplicación de Node usando contenedores de Docker. Hizo que su proyecto fuera más modular y portátil mediante la extracción de información confidencial y la desvinculación del estado de su aplicación del código de esta. También configuró un archivo docker-compose.yml estándar que podrá revisar a medida que sus necesidades y requisitos de desarrollo cambien.

      A medida que realice desarrollos, es posible que le interese aprender más sobre cómo diseñar aplicaciones para flujos de trabajo en contenedores y de Cloud Native. Consulte Crear aplicaciones para Kubernetes y Modernizar aplicaciones para Kubernetes para obtener más información sobre estos temas.

      Para obtener más información sobre el código utilizado en este tutorial, consulte Cómo crear una aplicación de Node.js con Docker y Cómo integrar MongoDB con su aplicación de Node. Para obtener información sobre cómo implementar una aplicación de Node con un proxy inverso de Nginx usando contenedores, consulte Cómo proteger una aplicación de Node.js en contenedor con Nginx, Let´s Encrypt y Docker Compose.



      Source link

      Dar los primeros pasos con redes definidas por software y crear una VPN con ZeroTier One


      Introducción

      Actualmente, cada vez se crean más proyectos de software a través de equipos cuyos miembros trabajan juntos desde ubicaciones geográficas apartadas. Aunque este flujo de trabajo ofrece muchas ventajas claras, existen casos en los cuales los equipos pueden desear vincular sus computadoras a través de Internet y tratarlas como si estuviesen en la misma habitación. Por ejemplo, es posible que pruebe sistemas distribuidos como Kubernetes o compilando una aplicación multiservicio compleja. A veces, contribuye a la productividad la posibilidad de tratar las máquinas como si estuviesen juntas, ya que no se debe correr el riesgo de exponer servicios inacabados a Internet. Este paradigma se puede lograr mediante la redes definidas por software (SDN), una tecnología relativamente nueva que proporciona una trama de red dinámica completamente integrada por software.

      ZeroTier One es una aplicación de código abierto que utiliza algunos de los últimos desarrollos en materia de SDN para permitir a los usuarios crear redes seguras y manejables, y tratar los dispositivos conectados como si estuviesen en la misma ubicación física. ZeroTier proporciona una consola web para software de administración de redes y de extremos para los clientes. Es una tecnología punto a punto cifrada, lo cual significa que a diferencia de lo que sucede con las soluciones de VPN tradicionales, las comunicaciones no tienen que pasar a través de un servidor o enrutador central; los mensajes se envían directamente de host a host. En consecuencia, es muy eficiente y garantiza una latencia mínima. Entre otros beneficios, se incluyen el proceso de implementación y configuración sencillo de ZeroTier, su fácil mantenimiento y el hecho de que permite registrar y administrar de forma centraliza los nodos autorizados a través de la consola web.

      Siguiendo este tutorial, conectará un cliente y un servidor juntos en una red punto a punto simple. Debido a que la red definida por software no utiliza el diseño tradicional de cliente y servidor, no se debe instalar ni configurar un servidor VPN central; esto agiliza la implementación de la herramienta y la adición de nodos complementarios. Una vez establecida la conectividad, tendrá la oportunidad de utilizar la capacidad VPN de ZeroTier empleando algunas funcionalidades inteligentes de Linux para permitir que el tráfico salga de su red ZerTier desde su servidor e indicar a un cliente que envíe su tráfico en esa dirección.

      Requisitos previos

      Antes de realizar este tutorial, necesitará los siguientes recursos:

      • Un servidor con Ubuntu 16.04. En este servidor, también necesitará un usuario no root con privilegios sudo que se pueda configurar usando nuestra guía de configuración inicial para servidores de Ubuntu 16.04.

      • Una cuenta con ZeroTier One, que puede configurar visitando MyZeroTier. Para realizar este tutorial, puede usar la versión gratuita de este servicio, sin costo ni compromisos.

      • Una computadora local para que se una a su SDN como cliente. En los ejemplos de este tutorial, tanto el servidor como el equipo local cuentan con Ubuntu Linux, pero cualquier sistema operativo listado en la página de descarga de ZeroTier funcionará en el cliente.

      Una vez cumplidos esos requisitos previos, estará listo para configurar redes definidas por software para su servidor y su equipo local.

      Paso 1: Crear una red definida por software usando ZeroTier One

      La plataforma ZeroTier proporciona el punto central de control para su red definida por software. En ella, podrá autorizar y desautorizar clientes, elegir un esquema de dirección y crear un ID de red al cual puede dirigir a sus clientes cuando los configure.

      Inicie sesión en su cuenta de ZerTier, haga clic en Networks en la parte superior de la pantalla y luego seleccione Create. Aparecerá un nombre de red generado automáticamente. Haga clic para ver la pantalla de configuración de su red. Tome nota del parámetro Network ID que aparece en amarillo, ya que necesitará consultarlo más tarde.

      Si prefiere cambiar el nombre de la red por algo más descriptivo, edite el nombre en el lado izquierdo de la pantalla; también puede añadir una descripción, si lo desea. Cualquier cambio que realice se guardará y se aplicará automáticamente.

      A continuación, seleccione el rango de dirección IPv4 en el que funcionará la SDN. En el lado derecho de la pantalla, en el área titulada IPv4 Auto-Assign, seleccione un rango de direcciones al que pertenecerán sus nodos. A los efectos de este tutorial, puede utilizarse cualquier rango, pero es importante dejar seleccionada la casilla Auto-Assign from Range.

      Asegúrese de que quede marcado el parámetro Certificate (Private Network) en Access Control, a la izquierda. Esto garantiza que solo las computadoras aprobadas puedan conectarse a su red, y que no lo haga cualquiera que conozca el ID de esta.

      Una vez que termine, sus ajustes deben tener un aspecto similar a este:

      Configurar ZeroTier

      En este punto, habrá creado con éxito los elementos básicos de una red definida por software de ZeroTier. A continuación, instalará el software de ZeroTier en su servidor y en más máquinas clientes para permitirles establecer conexión con su SDN.

      Paso 2: Instalar el cliente de ZeroTier One en su servidor y su máquina local

      Debido a que ZeroTier One es un software relativamente nuevo, aún no se ha incluido en los repositorios de software centrales de Ubuntu. Por este motivo, ZeroTier ofrece una secuencia de comandos de instalación que usaremos para instalar el software. Este comando es una secuencia de comandos con firma GPG, lo cual significa que el código que descargue se verificará como publicado por ZeroTier. Esta secuencia de comandos tiene cuatro partes principales. A continuación, se muestra una explicación de cada una de ellas:

      • curl -s 'https://pgp.mit.edu/pks/lookup?op=get&search=0x1657198823E52A61': esto importa la clave pública de ZeroTier de MIT.
      • gpg --import: esta sección del comando añade la clave pública de ZeroTier a su cadena de claves local de autoridades de confianza para los paquetes que intente instalar. La siguiente parte del comando solo se ejecutará si la importación de GPG se completa de forma correcta.
      • if z=$(curl -s 'https://install.zerotier.com/' | gpg); then echo "$z": en esta sección tienen lugar varios eventos. No obstante, el concepto es básicamente el siguiente: “Si GPG admite la secuencia de comandos de instalación con firma criptográfica descargada de ZeroTier.com y esta no se rechaza por no estar, en apariencia, firmada por ZeroTier, se debe pegar esa información en la pantalla”.
      • sudo bash; fi: esta sección toma la secuencia de comandos del instalador recién validado y lo ejecuta realmente antes de finalizar la rutina.

      Advertencia: Nunca debe descargar algo de Internet y asignarlo a otro programa a menos que esté seguro de que proviene de una fuente confiable. Si lo desea, puede inspeccionar el software de ZeroTier revisando el código fuente en la página oficial de GitHub del proyecto.

      Utilice una consola SSH para establecer conexión con su servidor recién creado y ejecute el siguiente comando como usuario normal (a continuación, verá una explicación del comando). Asegúrese de no ejecutarlo como root, ya que la secuencia de comandos solicita automáticamente su contraseña para elevar su nivel de privilegios, y recuerde mantener la consola de ZeroTier abierta en su navegador para poder interactuar con ella cuando sea necesario.

      • curl -s 'https://pgp.mit.edu/pks/lookup?op=get&search=0x1657198823E52A61' | gpg --import && if z=$(curl -s 'https://install.zerotier.com/' | gpg); then echo "$z" | sudo bash; fi

      Una vez que la secuencia de comandos se complete, verá dos líneas de resultado similares a las que se muestran a continuación. Tome nota de su dirección de ZeroTier (sin los corchetes) y del nombre del sistema que generó esa dirección; los necesitará más tarde.

      Output

      *** Waiting for identity generation... *** Success! You are ZeroTier address [ 916af8664d ].

      Repita este paso en su computadora local si utiliza Ubuntu, o siga los pasos pertinentes para su sistema operativo en la página de descargas del sitio web de ZeroTier. Una vez más, asegúrese de anotar la dirección de ZeroTier y la máquina que generó dicha dirección. Necesitará esta información en el siguiente paso de este tutorial cuando una su servidor y cliente a la red.

      Paso 3: Unirse a su red ZeroTier

      Ahora que tanto el servidor como el cliente tienen activo el software de ZeroTier, estará listo para conectarlos a la red que creó en la consola web de ZeroTier.

      Utilice el siguiente comando para indicar a su cliente que solicite acceso a la red ZeroTier a través de su plataforma. La solicitud inicial del cliente se rechazará y quedará pendiente, pero solucionaremos eso en un momento. Asegúrese de sustituir NetworkID por el ID de red que indicó anteriormente desde la ventana de configuración de su red.

      • sudo zerotier-cli join NetworkID

      Output

      200 join OK

      Recibirá un mensaje 200 join OK, lo cual confirmará que el servicio ZeroTier de su servidor pudo interpretar el comando. Si no lo hace, compruebe el ID de red de ZeroTier que introdujo.

      Debido a que no creó una red pública a la que pueda unirse cualquier persona en el mundo, deberá autorizar sus clientes. Vaya a la consola web de ZeroTier y desplácese hasta la parte inferior donde se encuentra la sección de miembros. Debería ver dos entradas marcadas como Online, con las mismas direcciones indicadas anteriormente.

      En la primera columna marcada como Auth?, seleccione las casillas para autorizarlas a unirse a la red. El controlador de ZeroTier asignará una dirección IP al servidor y el cliente del rango que seleccionó la próxima vez que invoquen a la SDN.

      Asignar las direcciones IP tomará un momento. Mientras espera, podría proporcionar un nombre corto y una descripción para sus nodos en la sección de miembros.

      De esta menra, habrá conectado dos sistemas a su red definida por software.

      Hasta ahora, se familiarizó con el panel de control de ZeroTier, usó la interfaz de la línea de comandos para descargar e instalar ZeroTier y luego conectó el servidor y el cliente a esa red. A continuación, comprobará que todo se haya aplicado correctamente realizando una prueba de conectividad.

      Paso 4: Verificar la conectividad

      En esta etapa, es importante validar que los dos hosts puedan comunicarse. Existe la posibilidad de que aún cuando parezca que los host se unieron a la red no puedan comunicarse. Al verificar la conectividad, no tendrá que preocuparse por problemas básicos de interconexión que podrían causar problemas posteriormente.

      Una alternativa sencilla para encontrar la dirección IP de ZeroTier de cada host es buscar en la sección de miembros de la consola web de ZeroTier. Es posible que necesite actualizarla tras autorizar al servidor y al cliente antes de que aparezcan sus direcciones IP. También puede usar la línea de comandos de Linux para buscar estas direcciones. Utilice el siguiente comando en ambos equipos; la primera dirección IP de la lista es la que debe usar. En el ejemplo que se muestra a continuación, esa dirección es 203.0.113.0.

      • ip addr sh zt0 | grep 'inet'

      Output

      inet 203.0.113.0/24 brd 203.0.255.255 scope global zt0 inet6 fc63:b4a9:3507:6649:9d52::1/40 scope global inet6 fe80::28e4:7eff:fe38:8318/64 scope link

      Para probar la conectividad entre los hosts, ejecute el comando ping desde un host seguido de la dirección del otro. Por ejemplo, en el cliente:

      Y en el servidor:

      Si el host opuesto muestra una respuesta (como se muestra en el resultado, a continuación), significa que los dos nodos se comunican correctamente a través de la SDN.

      Output

      PING 203.0.113.0 (203.0.113.0) 56(84) bytes of data. 64 bytes from 203.0.113.0: icmp_seq=1 ttl=64 time=0.054 ms 64 bytes from 203.0.113.0: icmp_seq=2 ttl=64 time=0.046 ms 64 bytes from 203.0.113.0: icmp_seq=3 ttl=64 time=0.043 ms

      Puede añadir tantos equipos como lo desee a esta configuración repitiendo los procesos de instalación e incorporación para ZeroTier anteriormente descritos. Recuerde que no es necesario que estos equipos estén cerca uno de otro.

      Ahora que confirmó que su servidor y cliente pueden comunicarse entre sí, continúe leyendo para aprender a configurar la red de modo que proporcione una puerta de enlace de salida y construir su propia VPN.

      Paso 5: Habilitar la capacidad VPN de ZeroTier

      Como se mencionó en la introducción, es posible usar ZeroTier como herramienta de VPN. Si no planea usar ZeroTier como una solución de VPN, no necesita seguir este paso y puede ir directo al 6.

      El uso de una VPN oculta el origen de sus comunicaciones con sitios web en Internet. Le permite omitir filtros y restricciones que puedan existir en la red que usa. Para Internet en general, parecerá que navega desde la dirección IP pública de su servidor. Para usar ZeroTier como herramienta de VPN, deberá aplicar algunos cambios más a las configuraciones de su servidor y cliente.

      Habilitar la traducción de la dirección de red y el reenvío de IP

      La traducción de direcciones de red, más comúnmente conocida como “NAT”, es un método por el cual un enrutador acepta paquetes en una interfaz etiquetada con la dirección IP del remitente y luego cambia esa dirección por la del enrutador. Se conserva un registro de este cambio en la memoria del enrutador para que cuando el tráfico de retorno vuelva en la dirección opuesta, el enrutador pueda traducir el IP de vuelta para su dirección original. La NAT se utiliza normalmente para permitir que varias computadoras funcionen detrás de una dirección IP públicamente expuesta, lo cual es útil para un servicio de VPN. Un ejemplo de la NAT en la práctica es el enrutador doméstico que su proveedor de servicios de Internet le proporcionó para conectar todos los dispositivos de su hogar a Internet. Su portátil, su teléfono, sus tabletas y cualquier otro dispositivo habilitado para Internet aparecerán para compartir la misma dirección IP pública en Internet, porque su enrutador aplica NAT.

      Aunque el enrutador normalmente aplica NAT, un servidor también puede hacerlo. A lo largo de este paso, utilizará esta funcionalidad en su servidor ZeroTier para habilitar sus capacidades de VPN.

      El reenvío de IP es una función que realiza un router o un servidor. Mediante esta se reenvía el tráfico de una interfaz a otra si las direcciones IP están en zonas diferentes. Si un enrutador se conectó a dos redes, el reenvío de IP le permite reenviar el tráfico entre ellas. Esto puede parecer sencillo, pero su implementación correcta puede ser un proceso sorprendentemente complejo. Sin embargo, en el caso de este tutorial, bastará con editar algunos archivos de configuración.

      Si se habilita el reenvío de IP, el tráfico de la VPN desde su cliente en la red ZeroTier llegará a la interfaz de ZeroTier del servidor. Sin estos cambios en la configuración, el kernel de Linux (por defecto) desechará cualquier paquete no destinado a la interfaz a la que llegan. Este es un comportamiento normal para el kernel de Linux, ya que normalmente cualquier paquete que llegue a una interfaz que tenga una dirección de destino para otra red podría ser el resultado de una mala configuración del direccionamiento en cualquier otra parte de la red.

      Resulta útil concebir el reenvío de IP como un proceso mediante el cual se notifica al kernel de Linux que es aceptable reenviar paquetes entre las interfaces. El ajuste predeterminado es 0, que equivale a “inhabilitado”. Lo cambiará a 1, equivalente a “habilitado”.

      Para ver la configuración actual, ejecute el siguiente comando:

      • sudo sysctl net.ipv4.ip_forward

      Output

      net.ipv4.ip_forward = 0

      Para habilitar el reenvío de IP, modifique el archivo /etc/sysctl.conf en su servidor y añada la línea requerida. Este archivo de configuración permite que un administrador anule los ajustes predeterminados del kernel y siempre se aplicará después de los reinicios para que no tenga que preocuparse por configurarlos de nuevo. Utilice nano o su editor de texto favorito para añadir la siguiente línea a la parte inferior del archivo.

      • sudo nano /etc/sysctl.conf

      /etc/sysctl.conf

      . . .
      net.ipv4.ip_forward = 1
      

      Guarde y cierre el archivo, y luego ejecute el siguiente comando para activar la adopción del kernel de la nueva configuración

      El servidor adoptará cualquier directiva nueva de configuración en el archivo y la aplicará de inmediato, sin necesidad de un reinicio. Ejecute el mismo comando que aplicó antes; verá que el reenvío de IP está habilitado.

      • sudo sysctl net.ipv4.ip_forward

      Output

      net.ipv4.ip_forward = 1

      Ahora que el reenvío de IP está habilitado, podrá aprovecharlo al máximo proporcionando al servidor algunas reglas de direccionamiento básicas. Debido a que el kernel de Linux ya tiene una capacidad de direccionamiento de red integrada, lo único que debe hacer es añadir algunas reglas para indicar al firewall integrado y al enrutador que el nuevo tráfico que verá es aceptable y mostrarle el destino de este.

      Para añadir estas reglas desde la línea de comandos, primero necesitará saber los nombres que Ubuntu asignó a su interfaz de ZeroTier y a su interfaz ethernet regular para Internet. Normalmente, son zt0 y eth0 respectivamente, aunque no siempre es así.

      Para encontrar estos nombres de interfaces, utilice el comando ip link show. Esta utilidad de línea de comandos es parte de iproute2, un conjunto de utilidades del espacio de usuario que viene instalada en Ubuntu por defecto:

      En el resultado de este comando, los nombres de las interfaces se encuentran directamente junto a los números que identifican una interfaz única en la lista. Estos nombres de interfaces se resaltan en el siguiente resultado de ejemplo. Si el suyo difiere de los nombres que se muestran en el ejemplo, sustituya el nombre de su interfaz de forma correspondiente a lo largo de esta guía.

      Output

      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 72:2d:7e:6f:5e:08 brd ff:ff:ff:ff:ff:ff 3: zt0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 2800 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000 link/ether be:82:8f:f3:b4:cd brd ff:ff:ff:ff:ff:ff

      Con esa información, utilice iptables para habilitar la traducción de direcciones de red y el enmascaramiento de IP:

      • sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

      Permita el reenvío de tráfico y el seguimiento de las conexiones activas:

      • sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

      A continuación, permita el reenvío de tráfico de zt0 a eth0. No es necesaria una regla inversa, ya que en este tutorial se supone que el cliente siempre realiza la invocación a través del servidor, y no al revés:

      • sudo iptables -A FORWARD -i zt0 -o eth0 -j ACCEPT

      Es importante recordar que las reglas de iptables que estableció para el servidor no persisten automáticamente entre los reinicios. Deberá guardar estas reglas para garantizar que se vuelvan a implementar si el servidor se reinicia. En su servidor, ejecute los comandos siguientes usando como guía las breves instrucciones en la pantalla para guardar las reglas IPv4 actuales; no se requiere IPv6.

      • sudo apt-get install iptables-persistent
      • sudo netfilter-persistent save

      Tras ejecutar sudo netfilter-persistent save, puede resultarle conveniente reiniciar su servidor para validar el almacenamiento correcto de las reglas de iptables. Una forma sencilla de verificar esto es ejecutar sudo iptables-save, que volcará la configuración actual cargada en la memoria a su terminal. Si ve reglas similares a las que se muestran a continuación en relación con el enmascaramiento, el reenvío y la interfaz zt0, significa que se guardaron correctamente.

      Output

      # Generated by iptables-save v1.6.0 on Tue Apr 17 21:43:08 2018 . . . -A POSTROUTING -o eth0 -j MASQUERADE COMMIT . . . -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i zt0 -o eth0 -j ACCEPT COMMIT . . .

      Ahora que estas reglas se aplicaron a su servidor, está listo para alternar el tráfico entre la red de ZeroTier y la Internet pública. Sin embargo, la VPN no funcionará a menos que la red ZeroTier esté informada de que el servidor está listo para utilizarse como una puerta de enlace.

      Habilitar su servidor para administrar la ruta global

      Para que su servidor procese el tráfico de cualquier cliente, debe asegurarse de que otros clientes de la red de ZeroTier puedan enviar su tráfico a él. Esto puede hacerse estableciendo una ruta global en la consola de ZeroTier. Aquellos que están familiarizados con las redes informáticas pueden describir esto como una Ruta predeterminada. Es donde cualquier cliente envía su tráfico predeterminado; es decir, cualquier tráfico que no debería ir a ninguna otra ubicación específica.

      Vaya a la parte superior derecha de su página de redes de ZeroTier y añada una nueva ruta con los parámetros siguientes. Puede encontrar el IP de ZeroTier para su servidor en la sección de miembros de su página de configuración de red de ZeroTier. En el campo network/bits, introduzca 0.0.0.0/0 y en el campo (LAN) la dirección IP de su servidor ZeroTier.

      Cuando se implementen los detalles, haga clic en el símbolo “+”; verá aparecer una nueva regla debajo de la existente. Un globo naranja en ella indicará que es de hecho una ruta global:

      Regla de ruta global

      Con su red de ZeroTier lista, solo queda configurar un aspecto para que la VPN funcione: los clientes.

      Configurar clientes de Linux

      Nota: Los comandos de esta sección solo se aplican a clientes de Linux. En la siguiente sección se proporcionan instrucciones para configurar clientes de Windows o macOS.

      Si su cliente cuenta con Linux, deberá realizar un cambio manual en su archivo /etc/sysctl.conf. Este cambio de configuración es necesario para modificar la perspectiva del kernel respecto de lo que es una ruta de retorno aceptable para el tráfico de su cliente. Debido a la forma en que se encuentra configurada la VPN de ZeroTier, a veces puede parecer que el tráfico que regresa de su servidor a su cliente proviene de una dirección de red diferente de aquella a la que se envió. Por defecto, el kernel de Linux considera que no son válidas y las desactiva, lo que hace necesario anular este comportamiento.

      Abra /etc/sysctl.conf en su máquina cliente:

      • sudo nano /etc/sysctl.conf

      A continuación, añada la siguiente línea:

      Output

      . . . net.ipv4.conf.all.rp_filter=2

      Guarde y cierre el archivo, y luego ejecute sudo sysctl -p para aplicar los cambios.

      A continuación, informe al software del cliente de ZerTier que su red tiene permiso para llevar tráfico de ruta predeterminado. Esto modifica el direccionamiento del cliente y, por tanto, se considera como una función privilegiada, por lo que debe habilitarse manualmente. El comando imprimirá una estructura de configuración en el resultado. Compruebe esto para confirmar que muestre allowDefault=1 en la parte superior:

      • sudo zerotier-cli set NetworkID allowDefault=1

      Si en cualquier momento desea dejar de usar ZeroTier como VPN con todo el enrutamiento de su tráfico a través de ella, fije allowDefault de nuevo en 0.

      • sudo zerotier-cli set NetworkID allowDefault=0

      Cada vez que se reinicia el servicio ZeroTier en el cliente, el valor allowDefault=1 vuelve a fijarse en 0. Por lo tanto, recuerde volver a ejecutarlo para activar la funcionalidad de VPN.

      Por defecto, el servicio de ZeroTier se establece para que se inicie automáticamente en el arranque para el cliente y el servidor de Linux. Si no desea que esto sea así, puede deshabilitar la rutina de inicio con el comando siguiente.

      • sudo systemctl disable zerotier-one

      Si desea usar otros sistemas operativos en su red ZeroTier, lea la siguiente sección. De lo contrario, continúe directamente con la sección “Administrar flujos”.

      Configurar clientes que no tengan Linux

      El software cliente de ZeroTier está disponible para muchos sistemas operativos, no solo para Linux; incluso es compatible con teléfonos inteligentes. Existen clientes para Windows, macOS, Android, iOS e incluso sistemas operativos especializados como QNAP, Synology y sistemas NAS de WesternDigital.

      Para unir clientes macOS y Windows a la red, inicie la herramienta ZeroTier (que instaló en el paso 1) e introduzca su ID de red en el campo proporcionado antes de hacer clic en Join. Recuerde volver a la consola de ZeroTier para seleccionar el botón Allow a fin de autorizar un nuevo host en su red.

      Asegúrese de seleccionar la casilla con la etiqueta Route all traffic through ZeroTier. Si no lo hace, su cliente se adjuntará a su red de ZeroTier, pero no intentará enviar su tráfico de Internet a través de ella.

      Utilice una herramienta de verificación de IP, como ICanHazIP para verificar que su tráfico aparezca a Internet desde la IP de su servidor. Para comprobarlo, pegue la siguiente URL en la barra de direcciones de su navegador. Este sitio web mostrará la dirección IP que su servidor (y el resto de Internet) ve que usted usa para acceder al sitio:

      http://icanhazip.com
      

      Una vez completados estos pasos, podrá comenzar a usar su VPN como lo desee. En la siguiente sección opcional, se abarca una tecnología integrada en la SDN de ZeroTier y conocida con la denominación “reglas de flujo”; no obstante, estas no son necesarias para que la funcionalidad de VPN se aplique.

      Paso 6: Administrar flujos (opcional)

      Uno de los beneficios de una red definida por software es el controlador centralizado. En cuanto a ZeroTier, el controlador centralizado es la Interfaz de usuario web que se sitúa por encima del servicio SDN general de ZeroTier. Desde esta interfaz, es posible escribir reglas conocidas como reglas de flujo que especifican lo que el tráfico de una red puede o no hacer. Por ejemplo, podría especificar una prohibición total en ciertos puertos de la red por los que pase tráfico a través la red, limitar los hosts que pueden comunicarse entre ellos e incluso redirigir el tráfico.

      Ésta es una capacidad extremadamente potente que se implementa casi al instante, ya que cualquier cambio aplicado a la tabla de flujos se envían a miembros de la red y surten efecto tras unos momentos. Para editar las reglas de flujo, vuelva a la interfaz de usuario web de ZeroTier, haga clic en la pestaña Networking, y desplácese hacia abajo hasta que vea un cuadro titulado Flow Rules (puede estar contraído, en cuyo caso deberá desplegarlo). Con esto, se abrirá un campo de texto en el que podrá introducir las reglas que desee. Existe, en la consola de ZeroTier, un manual completo en un cuadro que se halla justo debajo del cuadro de entrada de Flow Rules; lleva la leyenda Rules Engine Help.

      A continuación, se ofrecen algunas reglas de ejemplo para ayudarlo a explorar esta funcionalidad.

      Para bloquear cualquier tráfico vinculado al servidor DNS 8.8.8.8, añada esta regla:

      drop
          ipdest 8.8.8.8/32
      ;
      

      Para redirigir cualquier tráfico destinado al servidor de DNS público de Google a uno de sus nodos de ZeroTier, añada la regla siguiente. Esto podría servir como una regla general excelente para anular las búsquedas de DNS:

      redirect NetworkID
          ipdest 8.8.8.8/32
      ;
      

      Si su red tiene requisitos de seguridad especiales, puede detener cualquier actividad en los puertos FTP, Telnet y HTTP sin cifrado añadiendo esta regla:

      drop
          dport 80,23,21,20
      ;
      

      Cuando termine de añadir las reglas de flujo, haga clic en el botón Save Changes. ZeroTier registrará sus cambios.

      Conclusión

      A lo largo de este tutorial, dio un primer paso hacia el mundo de las redes definidas por software. Además, trabajar con ZeroTier proporciona cierta información sobre los beneficios de esta tecnología. Si siguió el ejemplo de VPN, aunque la configuración inicial puede contrastar con otras herramientas que haya usado en el pasado, la facilidad de añadir clientes podría ser una razón de peso para usar la tecnología en cualquier otro espacio.

      En resumen, aprendió a usar ZeroTier como su proveedor de SDN, y a configurar y conectar nodos a esa red. El elemento de VPN le habrá proporcionado un mayor nivel de comprensión de cómo funciona el direccionamiento en esa red y cualquier ruta de este tutorial le permitirá utilizar la potente tecnología de las reglas de flujo.

      Ahora que existe una red punto a punto, podría combinarla con otra funcionalidad como la de intercambio de archivos. Si dispone de un servidor NAS o de archivos en casa, podría vincularlo a ZeroTier y acceder a él en cualquier parte. Si desea compartirlo con sus amigos, puede mostrarles la forma de unirse a su red de ZeroTier. Los empleados distribuidos en una zona extensa podrían incluso establecer conexión con el mismo espacio central de almacenamiento. Si desea comenzar a compilar el intercambio de archivos para cualquiera de estos ejemplos, consulte Cómo configurar un recurso compartido de Samba para una pequeña organización en Ubuntu 16.04.



      Source link

      Cómo implementar una aplicación PHP con Kubernetes en Ubuntu 16.04


      El autor seleccionó Open Internet/Free Speech para recibir una donación como parte del programa Write for DOnations.

      Introducción

      Kubernetes es un sistema de orquestación de contenedores de código abierto. Le permite crear, actualizar y escalar contenedores sin preocuparse por el tiempo de inactividad.

      Para ejecutar una aplicación PHP, Nginx funciona como proxy para PHP-FPM. Disponer esta configuración en un solo contenedor puede ser un proceso engorroso, pero Kubernetes permitirá administrar ambos servicios en contenedores separados. Utilizar Kubernetes le permitirá lograr que sus contenedores sean reutilizables e intercambiables, y no tendrá que reconstruir la imagen de sus contenedores cada vez que haya una nueva versión de Nginx o PHP.

      En este tutorial, implementará una aplicación PHP 7 en un clúster de Kubernetes con Nginx y PHP-FPM ejecutándose en contenedores separados. También aprenderá a mantener los archivos de configuración y el código de aplicación fuera de la imagen de contenedor que utilice el sistema de almacenamiento en bloque de DigitalOcean. Este enfoque le permitirá reutilizar la imagen de Nginx para cualquier aplicación que necesite un servidor web o proxy pasando un volumen de configuración en lugar de reconstruir la imagen.

      Requisitos previos

      Paso 1: Crear los servicios de PHP-FPM y Nginx

      En este paso, creará los servicios de PHP-FPM y Nginx. Un servicio permite acceder a un conjunto de pods desde el interior del clúster. Los servicios dentro de un clúster pueden comunicarse de forma directa a través de sus nombres, sin necesidad de direcciones IP. El servicio de PHP-FPM permitirá el acceso a los pods de PHP-FPM, mientras que el servicio Nginx permitirá el acceso a los pods de Nginx.

      Debido a que los pods de Nginx representarán los pods de PHP-FPM, tendrá que indicar al servicio la forma de encontrarlos. En lugar de utilizar direcciones IP, aprovechará la detección automática de servicios de Kubernetes para utilizar nombres legibles para humanos para dirigir solicitudes al servicio apropiado.

      Para crear el servicio, creará un archivo de definición de objeto. Cada definición de objeto de Kubernetes es un archivo YAML que contiene por lo menos los siguientes elementos:

      • apiVersion: versión de la API de Kubernetes a la que pertenece la definición.
      • kind: objeto de Kubernetes que este archivo representa. Por ejemplo,un pod o service.
      • metadata: contiene el name del objeto junto con cualquier labels que desee aplicarle.
      • spec: contiene una configuración específica según el tipo de objeto que cree, como la imagen del contenedor o los puertos en los cuales se podrá acceder a este.

      Primero, creará un directorio para contener sus definiciones de objetos de Kubernetes.

      Aplique SSH a su *nodo maestro *y cree el directorio definitions que contendrá sus definiciones de objetos de Kubernetes.

      Acceda al directorio definitions recién creado:

      Realice su servicio de PHP-FPM creando un archivo php_service.yaml:

      Establezca kind como Service para especificar que este objeto es un servicio:

      php_service.yaml

      ...
      apiVersion: v1
      kind: Service
      

      Nombre el servicio como php, ya que proporcionará acceso a PHP-FPM:

      php_service.yaml

      ...
      metadata:
        name: php
      

      Agrupará de manera lógica diferentes objetos con etiquetas. En este tutorial, utilizará etiquetas para agrupar los objetos en “niveles”, como frontend o backend. Los pods de PHP se ejecutarán detrás de este servicio, por lo que le asignará la etiqueta tier: backend.

      php_service.yaml

      ...
        labels:
          tier: backend
      

      Un servicio determina los pods a los que se debe acceder utilizando las etiquetas selector. Se servirá un pod que coincida con estas etiquetas, independiente de que este se cree antes o después del servicio. Agregará etiquetas para sus pods posteriormente en el tutorial.

      Utilice la etiqueta tier: backend para asignar el pod al nivel de backend. También agregará la etiqueta app: php para especificar que este pod ejecuta PHP. Agregue estas dos etiquetas después de la sección de metadata.

      php_service.yaml

      ...
      spec:
        selector:
          app: php
          tier: backend
      

      A continuación, especifique el puerto utilizado para acceder a este servicio. En este tutorial, utilizará el puerto 9000. Añádalo al archivo php_service.yaml en spec:

      php_service.yaml

      ...
        ports:
          - protocol: TCP
            port: 9000
      

      Su archivo php_service.yaml completo tendrá el siguiente aspecto:

      php_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: php
        labels:
          tier: backend
      spec:
        selector:
          app: php
          tier: backend
        ports:
        - protocol: TCP
          port: 9000
      

      Presione CTRL + o para guardar el archivo y luego CTRL + x para cerrar nano.

      Ahora que ha creado la definición de objeto para su servicio, para ejecutar el servicio utilizará el comando kubectl apply junto con el argumento -f y especificará su archivo php_service.yaml.

      Cree su servicio:

      • kubectl apply -f php_service.yaml

      Este resultado confirma la creación del servicio:

      Output

      service/php created

      Verifique que su servicio esté en ejecución:

      Observará su servicio de PHP-FPM en ejecución:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10m php ClusterIP 10.100.59.238 <none> 9000/TCP 5m

      Kubernetes admite varios tipos de servicios. Su servicio php utiliza el tipo de servicio predeterminado, clusterIP. Este tipo de servicio asigna una IP interna y permite acceder al servicio sólo desde el interior del clúster.

      Ahora que el servicio de PHP-FPM está listo, creará el servicio de Nginx. Cree y abra un nuevo archivo llamado nginx_service.yaml con el editor:

      Este servicio se orientará a los pods de Nginx, por lo que lo llamará nginx. También agregará una etiqueta tier: backend, ya que pertenece al nivel de backend:

      nginx_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: nginx
        labels:
          tier: backend
      

      De modo similar al servicio php, dirija los pods con las etiquetas de selección app: nginx y tier: backend. Permita que sea posible acceder a este servicio en el puerto 80, el puerto HTTP predeterminado.

      nginx_service.yaml

      ...
      spec:
        selector:
          app: nginx
          tier: backend
        ports:
        - protocol: TCP
          port: 80
      

      Será posible acceder al servicio de Nginx de forma pública en Internet desde la dirección IP pública de su Droplet. Puede encontrar your_public_ip desde su panel en la nube de DigitalOcean. En spec.externalIPs, agregue lo siguiente:

      nginx_service.yaml

      ...
      spec:
        externalIPs:
        - your_public_ip
      

      Su archivo nginx_service.yaml tendrá este aspecto:

      nginx_service.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: nginx
        labels:
          tier: backend
      spec:
        selector:
          app: nginx
          tier: backend
        ports:
        - protocol: TCP
          port: 80
        externalIPs:
        - your_public_ip    
      

      Guarde y cierre el archivo. Cree el servicio de Nginx:

      • kubectl apply -f nginx_service.yaml

      Observará el siguiente resultado cuando el servicio esté en ejecución:

      Output

      service/nginx created

      Puede ver todos los servicios en funcionamiento ejecutando lo siguiente:

      Observará los servicios de PHP-FPM y Nginx enumerados en el resultado:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13m nginx ClusterIP 10.102.160.47 your_public_ip 80/TCP 50s php ClusterIP 10.100.59.238 <none> 9000/TCP 8m

      Tenga en cuenta que si desea eliminar un servicio puede ejecutar lo siguiente:

      • kubectl delete svc/service_name

      Ahora que creó sus servicios de PHP-FPM y Nginx, deberá especificar el lugar de almacenamiento para el código de su aplicación y sus archivos de configuración.

      Paso 2: Instalar el complemento de almacenamiento de DigitalOcean

      Kubernetes proporciona diversos complementos de almacenamiento que pueden crear el espacio de almacenamiento para su entorno. En este paso, instalará el complemento de almacenamiento de DigitalOcean para crear un almacén en bloques en DigitalOcean. Una vez completada la instalación, agregará una clase de almacenamiento llamada do-block-storage que utilizará para crear su almacenamiento en bloques.

      Primero configurará un objeto secreto de Kubernetes para almacenar su token de API de DigitalOcean. Los objetos secretos se utilizan para compartir información confidencial, como claves y contraseñas SSH, con otros objetos de Kubernetes dentro del mismo espacio de nombres. Los espacios de nombres ofrecen una alternativa lógica para separar sus objetos de Kubernetes.

      Abra un archivo llamado secret.yaml con el editor:

      Nombre su objeto secreto digitalocean y agréguelo al namespace kube-system. kube-system es el espacio de nombres predeterminado para los servicios internos de Kubernetes y el complemento de almacenamiento de DigitalOcean también lo utiliza para iniciar varios componentes.

      secret.yaml

      apiVersion: v1
      kind: Secret
      metadata:
        name: digitalocean
        namespace: kube-system
      

      En lugar de una clave spec, un secreto utiliza una clave data o stringData para contener la información necesaria. El parámetro data contiene datos codificados en base64 que se decodifican de manera automática cuando se recuperan. El parámetro stringData contiene datos no codificados que se codifican de manera automática durante la creación o actualización, y no muestra los datos al recuperar secretos. En este tutorial, utilizará stringData para una mayor practicidad.

      Agregue el access-token como stringData:

      secret.yaml

      ...
      stringData:
        access-token: your-api-token
      

      Guarde y cierre el archivo.

      Su archivo secret.yaml tendrá el siguiente aspecto:

      secret.yaml

      apiVersion: v1
      kind: Secret
      metadata:
        name: digitalocean
        namespace: kube-system
      stringData:
        access-token: your-api-token
      

      Cree el secreto:

      • kubectl apply -f secret.yaml

      Verá este resultado al crear secretos:

      Output

      secret/digitalocean created

      Puede visualizar el secreto con el siguiente comando:

      • kubectl -n kube-system get secret digitalocean

      El resultado tendrá un aspecto similar a este:

      Output

      NAME TYPE DATA AGE digitalocean Opaque 1 41s

      El tipo Opaque implica que este secreto es de sólo lectura, parámetro estándar para los secretos de stringData. Puede obtener más información más acerca de ello en las especificaciones de diseño de secretos. El campo DATA muestra el número de elementos almacenados en este secreto. En este caso, muestra 1 porque tiene una sola clave almacenada.

      Ahora que su secreto está implementado, instale el complemento de almacenamiento en bloques de DigitalOcean:

      • kubectl apply -f https://raw.githubusercontent.com/digitalocean/csi-digitalocean/master/deploy/kubernetes/releases/csi-digitalocean-v0.3.0.yaml

      Verá resultados similares al siguiente:

      Output

      storageclass.storage.k8s.io/do-block-storage created serviceaccount/csi-attacher created clusterrole.rbac.authorization.k8s.io/external-attacher-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-attacher-role created service/csi-attacher-doplug-in created statefulset.apps/csi-attacher-doplug-in created serviceaccount/csi-provisioner created clusterrole.rbac.authorization.k8s.io/external-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-provisioner-role created service/csi-provisioner-doplug-in created statefulset.apps/csi-provisioner-doplug-in created serviceaccount/csi-doplug-in created clusterrole.rbac.authorization.k8s.io/csi-doplug-in created clusterrolebinding.rbac.authorization.k8s.io/csi-doplug-in created daemonset.apps/csi-doplug-in created

      Ahora que instaló el complemento de almacenamiento de DigitalOcean, puede crear almacenamiento en bloques para contener el código de su aplicación y sus archivos de configuración.

      Paso 3: Crear el volumen persistente

      Con su secreto implementado y el complemento de almacenamiento en bloques instalado, estará listo para crear su volumen persistente. Un volumen persistente, o VP, es un almacenamiento en bloques de un tamaño especifico que es independiente del ciclo de vida de un pod. Utilizar un volumen persistente le permitirá administrar o actualizar sus pods sin preocuparse por la posibilidad de perder el código de su aplicación. El acceso a un volumen persistente es posible utilizando un PersistentVolumeClaim, o PVC, que monta el VP en la ruta requerida.

      Abra un archivo llamado code_volume.yaml con su editor:

      Dé el nombre code al PVC agregando los siguientes parámetros y valores a su archivo:

      code_volume.yaml

      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: code
      

      La spec para un PVC contiene los siguientes elementos:

      • accessModes que varían según el caso de uso. Son los siguientes:
        • ReadWriteOnce: monta el volumen con atributos de lectura y escritura para un solo nodo.
        • ReadOnlyMany: monta el volumen con atributos de sólo lectura para muchos nodos.
        • ReadWriteMany: monta el volumen con atributos de lectura y escritura para muchos nodos.
      • resources: espacio de almacenamiento que usted requiere.

      El almacenamiento en bloques de DigitalOcean solo se monta en un único nodo, por lo que usted fijará accessModes en ReadWriteOnce. Este tutorial le servirá de guía para agregar una pequeña cantidad de código de aplicación. Por lo tanto 1 gigabyte será suficiente en este caso de uso. Si planea almacenar una mayor cantidad de código o datos en el volumen, puede modificar el parámetro storage para que se adapte a sus necesidades. Puede aumentar la cantidad de almacenamiento después de crear el volumen, pero no es posible reducir el disco.

      code_volume.yaml

      ...
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
      

      A continuación, especifique la clase de almacenamiento que Kubernetes utilizará para proporcionar los volúmenes. Utilizará la clase do-block-storage creada por el complemento de almacenamiento en bloques de DigitalOcean.

      code_volume.yaml

      ...
        storageClassName: do-block-storage
      

      Su archivo code_volume.yaml tendrá el siguiente aspecto:

      code_volume.yaml

      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: code
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: do-block-storage
      

      Guarde y cierre el archivo.

      Cree el elemento code PersistentVolumeClaim utilizando kubectl:

      • kubectl apply -f code_volume.yaml

      El siguiente resultado le indica que el objeto se creó de forma correcta y está listo para montar su PVC de 1 GB como un volumen.

      Output

      persistentvolumeclaim/code created

      Para visualizar volúmenes persistentes (VP) disponibles:

      Verá su VP enumerado:

      Output

      NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-ca4df10f-ab8c-11e8-b89d-12331aa95b13 1Gi RWO Delete Bound default/code do-block-storage 2m

      A excepción de Reclaim Policy y Status, en los campos anteriores se muestra un resumen de su archivo de configuración. Reclaim Policy define lo que se hace con el VP después de que el PVC que lo accede es eliminado. Delete elimina el VP de Kubernetes y la infraestructura de DigitalOcean. Puede obtener más información sobre Reclaim Policy y Status en la documentación de VP de Kubernetes.

      De esta manera, habrá creado correctamente un volumen persistente utilizando el complemento de almacenamiento en bloques de DigitalOcean. Ahora que su volumen persistente está listo, creará sus pods con una implementación.

      Paso 4: Crear una implementación de PHP-FPM

      En este paso, aprenderá a utilizar una implementación para crear su pod de PHP-FPM. Las implementaciones proporcionan una manera uniforme de crear, actualizar y administrar pods utilizando ReplicaSets. Si una actualización no funciona como se espera, una implementación restaurará de manera automática sus pods a una imagen anterior.

      La clave de despliegue spec.selector enumerará las etiquetas de los pods que administrará. También utilizará la clave template para crear los pods necesarios.

      En este paso también se introducirá el uso de contenedores Init. Los contenedores Init ejecutan uno o más comandos antes que los contenedores regulares especificados en la clave template del pod. En este tutorial, su contenedor Init obtendrá un archivo de muestra de index.php de GitHub Gist utilizando wget. El archivo de muestra contiene lo siguiente:

      index.php

      <?php
      echo phpinfo();
      

      Para crear su implementación, abra un nuevo archivo llamado php_deployment.yaml con su editor:

      Esta implementación administrará sus pods de PHP-FPM, por lo que dará al objeto de implementación el nombre php. Los pods pertenecen al nivel de backend, por lo que agrupará la implementación en este grupo utilizando la etiqueta tier: backend:

      php_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: php
        labels:
          tier: backend
      

      Para la implementación spec, especificará la cantidad de copias de este pod que se crearán utilizando el parámetro replicas. El número de replicas variará según sus necesidades y los recursos disponibles. En este tutorial, creará una réplica:

      php_deployment.yaml

      ...
      spec:
        replicas: 1
      

      La implementación administrará pods que coincidan con app: php y las etiquetas de tier: backend. En la clave de selector agregue:

      php_deployment.yaml

      ...
        selector:
          matchLabels:
            app: php
            tier: backend
      

      A continuación, la implementación spec requiere el elemento template para la definición de objeto de su pod. Esta plantilla definirá las especificaciones a partir de las cuales se creará el pod. Primero, agregará las etiquetas que se especificaron para el servicio php selectors y las matchLabels de la implementación. Agregue app: php y tier: backend en template.metadata.labels:

      php_deployment.yaml

      ...
        template:
          metadata:
            labels:
              app: php
              tier: backend
      

      Un pod puede tener varios contenedores y volúmenes, pero cada uno requerirá un nombre. Puede montar de manera selectiva volúmenes en un contenedor especificando una ruta de montaje para cada volumen.

      Primero, especifique los volúmenes a los que accederá su contenedor. Creó un PVC llamado code para contener el código de su aplicación. Por lo tanto dé el nombre code a este volumen también. En spec.template.spec.volumes, agregue lo siguiente:

      php_deployment.yaml

      ...
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
      

      A continuación, especifique el contenedor que desee ejecutar en este pod. Puede encontrar varias imágenes en la tienda de Docker, pero en este tutorial empleará la imagen php:7-fpm.

      En spec.template.spec.containers, agregue lo siguiente:

      php_deployment.yaml

      ...
            containers:
            - name: php
              image: php:7-fpm
      

      A continuación, montará los volúmenes a los que el contenedor solicita acceso. Este contenedor ejecutará su código PHP, de modo que deberá acceder al volumen code. También utilizará mountPath para especificar /code como punto de montaje.

      En spec.template.spec.containers.volumeMounts, agregue lo siguiente:

      php_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      Ahora que montó su volumen, debe introducir el código de su aplicación en el volumen. Es posible que para hacerlo haya utilizado previamente FTP/SFTP o clonado el código a través de una conexión SSH, pero en este paso verá la forma de copiar el código utilizando un contenedor Init.

      Según la complejidad de su proceso de configuración, puede utilizar un solo initContainer para ejecutar una secuencia de comandos que construya su aplicación, o puede utilizar un initContainer por comando. Asegúrese de que los volúmenes se monten en el initContainer.

      En este tutorial, utilizará un contenedor Init único con busybox para descargar el código. busybox es una pequeña imagen que contiene la utilidad wget que utilizará para hacerlo.

      En spec.template.spec, agregue su initContainer y especifique la imagen busybox:

      php_deployment.yaml

      ...
            initContainers:
            - name: install
              image: busybox
      

      Su contenedor Init necesitará acceso al volumen code para que pueda descargar el código en esa ubicación. En spec.template.spec.initContainers, monte el volumen code en la ruta /code:

      php_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      Cada contenedor Init debe ejecutar un command. Su contenedor Init utilizará wget para descargar el código de Github en el directorio de trabajo /code. La opción -O asigna un nombre al archivo descargado, y usted dará a este archivo el nombre index.php.

      Nota: Asegúrese de el código que extraerá sea confiable. Antes de introducir el código fuente en su servidor, inspecciónelo para asegurarse de que las funciones que realiza le parezcan adecuadas.

      En el contenedor install en spec.template.spec.initContainers, agregue estas líneas:

      php_deployment.yaml

      ...
              command:
              - wget
              - "-O"
              - "/code/index.php"
              - https://raw.githubusercontent.com/do-community/php-kubernetes/master/index.php
      

      Su archivo php_deployment.yaml completo tendrá este aspecto:

      php_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: php
        labels:
          tier: backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: php
            tier: backend
        template:
          metadata:
            labels:
              app: php
              tier: backend
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
            containers:
            - name: php
              image: php:7-fpm
              volumeMounts:
              - name: code
                mountPath: /code
            initContainers:
            - name: install
              image: busybox
              volumeMounts:
              - name: code
                mountPath: /code
              command:
              - wget
              - "-O"
              - "/code/index.php"
              - https://raw.githubusercontent.com/do-community/php-kubernetes/master/index.php
      

      Guarde el archivo y salga del editor.

      Cree la implementación de PHP-FPM con kubectl:

      • kubectl apply -f php_deployment.yaml

      Visualizará el siguiente resultado al crear la implementación:

      Output

      deployment.apps/php created

      A modo de resumen, esta implementación se iniciará descargando las imágenes especificadas. Luego, solicitará el PersistentVolume de su PersistentVolumeClaim y ejecutará en serie sus initContainers. Una vez completado el proceso, los contenedores ejecutarán y montarán los volumes en punto de montaje especificado. Una vez que todos estos pasos se hayan completado, su pod estará listo y en ejecución.

      Puede ver su implementación ejecutando lo siguiente:

      Verá el siguiente resultado:

      Output

      NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE php 1 1 1 0 19s

      Este resultado puede ayudarle a comprender el estado actual de la implementación. Deployment es uno de los controladores que conservan un estado deseado. El elemento template que creó especifica que un elemento replicas del pod del estado DESIRED se llamará php. El campo CURRENT indica cuántas réplicas se encuentran en ejecución, por lo que debe coincidir con el estado DESIRED. Puede leer información sobre los campos restantes en la documentación de implementación de Kubernetes.

      Puede ver los pods iniciados por esta implementación con el siguiente comando:

      El resultado de este comando varía según el tiempo transcurrido desde la creación de la implementación. Si la ejecuta poco después de su creación, el resultado probablemente tendrá este aspecto:

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-bf8zd 0/1 Init:0/1 0 9s

      Las columnas representan la siguiente información:

      • Ready: número de replicas que ejecutan este pod.
      • Status: estado del pod. Init indica que los contenedores Init están en ejecución. En este resultado, del total de 1 contenedor Init, ninguno terminó de ejecutarse.
      • Restarts: cantidad de veces que este proceso se reinició para iniciar el pod. Este número aumentará si alguno de sus contenedores Init falla. La implementación se reiniciará hasta que alcance un estado deseado.

      Según de la complejidad de sus secuencias de comandos de inicio, pueden pasar algunos minutos hasta que el estado cambie a podInitializing:

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-lkwgn 0/1 podInitializing 0 39s

      Esto significa que los contenedores Init han finalizado y que se están iniciando los contenedores. Si ejecuta el comando cuando todos los contenedores estén en ejecución, verá que el estado de pod cambiará a Running.

      Output

      NAME READY STATUS RESTARTS AGE php-86d59fd666-lkwgn 1/1 Running 0 1m

      Ahora verá que su pod se ejecuta de forma correcta. Si su pod no se inicia, puede realizar una depuración con los siguientes comandos:

      • Ver información detallada de un pod:
      • kubectl describe pods pod-name
      • Ver registros generados por un pod:
      • Ver registros para un contenedor específico en un pod:
      • kubectl logs pod-name container-name

      Su código de aplicación está montado y el servicio de PHP-FPM ya está listo para manejar conexiones. Ahora podrá crear su implementación de Nginx.

      Paso 5: Crear la implementación de Nginx

      En este paso, utilizará un ConfigMap para configurar Nginx. Un ConfigMap contiene su configuración en un formato de clave-valor al que puede hacer referencia en otras definiciones de objetos de Kubernetes. Este enfoque le brindará la flexibilidad necesaria para reutilizar o cambiar la imagen con una versión de Nginx distinta si es necesario. Actualizar ConfigMap replicará los cambios de manera automática en cualquier pod montado en él.

      Con su editor, cree un archivo nginx_configMap.yaml para su ConfigMap:

      • nano nginx_configMap.yaml

      Dé el nombre nginx-config a ConfigMap y agrúpelo en el microservicio de tier: backend:

      nginx_configMap.yaml

      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: nginx-config
        labels:
          tier: backend
      

      A continuación, agregará data para ConfigMap. Dé a la clave el nombre config y añada el contenido de su archivo de configuración de Nginx como valor. Puede utilizar el ejemplo de configuración de Nginx de este tutorial.

      Debido a que Kubernetes puede dirigir solicitudes al host adecuado para un servicio, puede ingresar el nombre de su servicio de PHP-FPM en el parámetro fastcgi_pass en lugar de su dirección IP. Agregue lo siguiente a su archivo nginx_configMap.yaml:

      nginx_configMap.yaml

      ...
      data:
        config : |
          server {
            index index.php index.html;
            error_log  /var/log/nginx/error.log;
            access_log /var/log/nginx/access.log;
            root ^/code^;
      
            location / {
                try_files $uri $uri/ /index.php?$query_string;
            }
      
            location ~ .php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_pass php:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
              }
          }
      

      Su archivo nginx_configMap.yaml tendrá el siguiente aspecto:

      nginx_configMap.yaml

      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: nginx-config
        labels:
          tier: backend
      data:
        config : |
          server {
            index index.php index.html;
            error_log  /var/log/nginx/error.log;
            access_log /var/log/nginx/access.log;
            root /code;
      
            location / {
                try_files $uri $uri/ /index.php?$query_string;
            }
      
            location ~ .php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+.php)(/.+)$;
                fastcgi_pass php:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
              }
          }
      

      Guarde el archivo y salga del editor.

      Cree el ConfigMap:

      • kubectl apply -f nginx_configMap.yaml

      Verá lo siguiente:

      Output

      configmap/nginx-config created

      Con esto, habrá terminado de crear su ConfigMap y ya podrá crear su implementación de Nginx.

      Comience abriendo un nuevo archivo nginx_deployment.yaml en el editor:

      • nano nginx_deployment.yaml

      Dé el nombre nginx a la implementación y añada la etiqueta tier: backend:

      nginx_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx
        labels:
          tier: backend
      

      Especifique que desea un elemento replicas en la implementación spec. Esta implementación administra pods con etiquetas app: nginx y tier: backend. Agregue los siguientes parámetros y valores:

      nginx_deployment.yaml

      ...
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nginx
            tier: backend
      

      A continuación, agregue el elemento template del pod. Debe utilizar las mismas etiquetas que agregó para selector.matchLabels de la implementación. Agregue lo siguiente:

      nginx_deployment.yaml

      ...
        template:
          metadata:
            labels:
              app: nginx
              tier: backend
      

      Habilite el acceso de Nginx al PVC code que creó previamente. En spec.template.spec.volumes, agregue lo siguiente:

      nginx_deployment.yaml

      ...
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
      

      Los pods pueden montar un ConfigMap como un volumen. Especificar un nombre de archivo y una clave creará un archivo con su valor como el contenido. Para usar ConfigMap, fije path en el nombre del archivo que tendrá el contenido de key. EL objetivo es crear un archivo site.conf a partir de la clave config. En spec.template.spec.volumes, agregue lo siguiente:

      nginx_deployment.yaml

      ...
            - name: config
              configMap:
                name: nginx-config
                items:
                - key: config
                  path: site.conf
      

      Advertencia: Si no se especifica un archivo, el contenido de key sustituirá el elemento mountPath del volumen. Esto quiere decir que si una ruta no está especificada de manera explícita, perderá todo el contenido de la carpeta de destino.

      A continuación, especificará la imagen a partir de la cual se creará su pod. En este tutorial se utilizará la imagen nginx:1.7.9 por cuestiones de estabilidad, pero puede encontrar otras imágenes de Nginx en la tienda de Docker. Además, debe hacer que Nginx esté disponible en el puerto 80. En spec.template.spec, agregue lo siguiente:

      nginx_deployment.yaml

      ...
            containers:
            - name: nginx
              image: nginx:1.7.9
              ports:
              - containerPort: 80
      

      Nginx y PHP-FPM deben acceder al archivo en la misma ruta. Por ello, monte el volumen de code en /code:

      nginx_deployment.yaml

      ...
              volumeMounts:
              - name: code
                mountPath: /code
      

      La imagen nginx:1.7.9 cargará de manera automática cualquier archivo de configuración en el directorio /etc/nginx/conf.d. Si se monta el volumen de config en este directorio, se creará el archivo /etc/nginx/conf.d/site.conf. En volumeMounts, agregue lo siguiente:

      nginx_deployment.yaml

      ...
              - name: config
                mountPath: /etc/nginx/conf.d
      

      Su archivo nginx_deployment.yaml tendrá el siguiente aspecto:

      nginx_deployment.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx
        labels:
          tier: backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nginx
            tier: backend
        template:
          metadata:
            labels:
              app: nginx
              tier: backend
          spec:
            volumes:
            - name: code
              persistentVolumeClaim:
                claimName: code
            - name: config
              configMap:
                name: nginx-config
                items:
                - key: config
                  path: site.conf
            containers:
            - name: nginx
              image: nginx:1.7.9
              ports:
              - containerPort: 80
              volumeMounts:
              - name: code
                mountPath: /code
              - name: config
                mountPath: /etc/nginx/conf.d
      

      Guarde el archivo y salga del editor.

      Cree la implementación de Nginx:

      • kubectl apply -f nginx_deployment.yaml

      El siguiente resultado indica que se creó su implementación:

      Output

      deployment.apps/nginx created

      Enumere sus implementaciones con el siguiente comando:

      Visualizará las implementaciones de Nginx y PHP-FPM:

      Output

      NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 1 1 1 0 16s php 1 1 1 1 7m

      Enumere los pods administrados por ambas implementaciones:

      Visualizará los pods que estén en ejecución:

      Output

      NAME READY STATUS RESTARTS AGE nginx-7bf5476b6f-zppml 1/1 Running 0 32s php-86d59fd666-lkwgn 1/1 Running 0 7m

      Ahora que todos los objetos de Kubernetes están activos, podrá visitar el servicio de Nginx en su navegador.

      Enumere los servicios en ejecución:

      • kubectl get services -o wide

      Obtenga la IP externa para su servicio de Nginx:

      Output

      NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 39m <none> nginx ClusterIP 10.102.160.47 your_public_ip 80/TCP 27m app=nginx,tier=backend php ClusterIP 10.100.59.238 <none> 9000/TCP 34m app=php,tier=backend

      En su navegador, visite su servidor escribiendo http://your_public_ip. Verá el resultado de php_info() y habrá confirmado que sus servicios de Kubernetes están configurados y activos.

      Conclusión

      A través de esta guía , cargó en contenedores los servicios de PHP-FPM y Nginx para poder administrarlos de manera independiente. Este enfoque no solo mejorará la escalabilidad de su proyecto a medida que amplíe sus capacidades, sino también le permitirá utilizar los recursos de manera eficaz. También almacenó el código de su aplicación en un volumen para poder actualizar sus servicios de manera sencilla en el futuro.



      Source link