Nginx como PHP moderno: Parte 2/3

Nginx como PHP moderno (Parte 2)

Nginx: En la primera parte de esta serie, creamos una aplicación PHP muy simple como ejemplo de algo que se le podría pedir a un programador freelance. Pero si el cliente aún no tiene un sitio web orientado a Internet y parte de su proyecto es crear uno, este artículo es para usted.

Nginx


Supongamos que su cliente ha superado su sitio Wix o Squarespace y quiere formularios respaldados por una base de datos o acceso a otras API y ha decidido que tener un sitio web personalizado de pila completa es para ellos. Mi artículo anterior mostró cómo hacer un formulario respaldado por una base de datos, y si puede hacer un formulario, puede hacer una docena. También mostró cómo implementarlo en la nube utilizando una máquina virtual de Google Cloud Platform (GCP) que se puede obtener por un precio relativamente bajo (asumiendo que no tiene mucho tráfico).

El único problema es que no está “endurecido por la batalla”. Cuando instalé la aplicación PHP en la máquina virtual y le di al mundo acceso a ella, un escaneo rápido de los registros mostró que estaba siendo investigado en busca de vulnerabilidades, presumiblemente por jugadores nefastos que buscaban un lugar para alojar contenido ilícito. Tampoco tenía HTTPS, por lo que no sería recomendable tener una página de inicio de sesión. Necesitamos un proxy inverso que pueda manejar esos aspectos. En este artículo, usaremos Nginx. En un artículo futuro, también usaremos Nginx para implementar un inicio de sesión único para todas sus aplicaciones.

Para seguir este artículo, necesitará un nombre de dominio, una cuenta de Cloudflare y una cuenta de GCP. Si se tratara de un verdadero proyecto independiente, su cliente se lo proporcionaría o podría asesorarlo sobre cómo configurarlo. Probablemente pueda usar cualquier servicio en la nube que permita VM desnudas con acceso SSH y HTTPS, solo estoy usando GCP aquí como ejemplo. No entraré en detalles sobre la configuración de Cloudflare y GCP; tienen mucha ayuda en línea para configurarlo y son relativamente simples para casos de uso simples como el nuestro. Si necesita obtener un nombre de dominio, asegúrese de obtener uno que le permita configurar Cloudflare como su DNS primario y secundario y que se sienta cómodo jugando con los registros DNS. Utilizo Namecheap.com y pude configurarlo sin problemas.

Lo primero que deberá hacer es aprovisionar una máquina virtual en GCP con un sistema operativo Linux moderno y aproximadamente 10 Gigabytes de espacio en el disco duro. Usé Ubuntu 20.04. Para lo que estamos haciendo, puede aprovisionar la CPU más pequeña que ofrecen. Asegúrese de darle acceso HTTPS y SSH externo (GCP agrega automáticamente acceso SSH). Una vez que su VM esté en funcionamiento, conéctese e instale Docker (y Docker Compose) y Git. Esas son las únicas dos cosas que necesitará instalar, por lo que el reaprovisionamiento debería ser muy sencillo en caso de que alguna vez lo necesite.

Deberá averiguar la dirección IP externa de su nueva máquina virtual para poder agregarla a Cloudflare.

Nginx

En la imagen de arriba, la segunda dirección IP es la dirección externa. Ahora puede ir a su cuenta de Cloudflare en la pestaña DNS y agregar un registro “A” con un nuevo nombre de host de su elección. Esto se antepondrá al nombre de dominio del host completo. Tenga en cuenta que esta dirección IP es “efímera” y podría cambiar entre reinicios de VM. Puede configurarlo en estático por un pequeño costo adicional.

Nginx

Ahora vaya a la pestaña SSL / TLS en Cloudflare y elija el modo Completo (estricto), de modo que todo su tráfico requiera https y su servidor de origen requerirá un certificado de Cloudflare.

Nginx

En la subpestaña “servidor de origen”, puede crear un certificado para volver a utilizarlo en su máquina virtual. Haga clic en el botón Crear certificado, tome los valores predeterminados para las preguntas y debería darle dos bloques de texto largo, el certificado y la clave. Mantenga esta página abierta y vaya a la pantalla SSH de su VM. Aún debería estar en su directorio de inicio y los copiaré allí. Usando VI (ya que es el único editor disponible en la nueva VM, abra  yourdomain.com.pem (sustituya su nombre de dominio real), inserte todo el texto del primer bloque y guárdelo. A continuación, abra yourdomain.com.key y haz lo mismo para el segundo bloque. Ahora tienes tu certificado / par de claves disponible en tu VM. También debes almacenar una copia de los dos archivos en un lugar seguro. ¡Simplemente no lo ponga ‘accidentalmente’ en GitHub!

Vamos a instalar nuestra aplicación a través de Git y Docker. Creé un repositorio en GitHub llamado phpappprod para contener tanto la configuración de Nginx como el docker-compose.yml que dice qué aplicaciones deben ejecutarse. Cuando creamos archivos en este repositorio, asumiremos que todos los archivos secretos se almacenan directamente en el padre, por lo que no se almacenarán accidentalmente en GitHub. Clona el nuevo repositorio en tu máquina local.

Por ahora, centrémonos en Nginx. Aquí hay un docker-compose.yml básico para iniciar Nginx:

version: '3.1'
services:
  nginx:
    image: nginx
    restart: always
    volumes:
     - ./nginx.conf:/etc/nginx/nginx.conf
     - ..:/etc/certs
    ports:
     - "443:443"

Observe que estamos mapeando /etc/certs.. para que busque en el directorio principal los certificados. El nginx.conf no contendrá ningún secreto, por lo que lo agregaremos al repositorio phpappprod GitHub y lo mapearemos directamente desde allí. Esto es lo que hay en nginx.conf por ahora.

events {}
http {
  server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /etc/certs/yourdomain.com.pem;
    ssl_certificate_key /etc/certs/yourdomain.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    location ~ ^/(html|images|javascript|js|css|flash|media|static)/ {
      index index.html;
      root /etc/nginx/html;
      expires 3s;
      include /etc/nginx/mime.types;
    }
  }
}

Obviamente, sustituya su propio dominio “tudominio.com” por server_name, ssl_certificate y ssl_certificate_key.

Antes de enviarlos a GitHub, puede probarlo todo localmente.

docker-compose up -d
curl -k https://localhost/

Debería obtener la página Nginx 404 ya que no tiene Nginx vinculado a su aplicación (y su aplicación no se está ejecutando). Tienes que usar la opción -k porque el certificado que creaste solo es bueno en Cloudflare. Cloudflare tendrá su propio certificado cuando acceda a su nuevo dominio en Internet.

Confirme y devuelva todo a GitHub. Ahora, de vuelta en la terminal SSH de su VM, puede clonar el repositorio phpappprod. Usaremos este repositorio para “transferir” todos los archivos no secretos a su VM. Utilice el modo HTTPS para clonar en lugar del modo SSH, ya que no realizará cambios desde la VM y no querrá tener que configurar claves SSH. Luego cambie el directorio al repositorio e inicie su servidor

cd phpappprod
docker-compose up -d

Ahora puede encontrar el servidor en Internet con un certificado SSH auténtico:

Nginx

Puede que esto no parezca muy impresionante, pero es un gran paso hacia la publicación de su aplicación en Internet con cierto nivel de seguridad.

 

Añadiendo su aplicación

En [el artículo anterior] de esta serie, teníamos un sitio web simple con solo unas pocas páginas. Había una página que mostraba combinaciones de vino / queso y una página que le permitía agregar más. No mucho, pero es un buen comienzo. Una vez que tenga un par de páginas y un tema general, estará en una buena parte del camino hacia una aplicación web decente. Ya verificó que puede leer y escribir desde la base de datos y mostrarla en la Web.

Hubo algunos problemas que dejamos abiertos, la aplicación del modo de desarrollo tenía una contraseña de ejemplo incorporada en el sistema. También requirió un entorno de desarrollo para configurar la base de datos. Y finalmente, la base de datos se almacenó en el contenedor de la base de datos, por lo que una vez que el contenedor desapareció, los datos también lo hicieron. Podemos solucionar todos estos problemas a medida que avanzamos hacia la publicación de la aplicación en Internet.

Una nota al margen rápida, es posible que no desee que su base de datos se ejecute en un contenedor. Esa es una elección razonable. Depende de usted instalar y configurar la base de datos en la máquina virtual, o incluso en una máquina virtual separada. GCP tiene un instalador de MySQL de apuntar y hacer clic que activará una base de datos completamente configurada en una máquina virtual. Pero soy un fanático del control, y el resto de este artículo asumirá que ejecutará la base de datos en un contenedor. Si no quiere eso, tendrá que averiguar cómo ajustar la aplicación para leer la base de datos de la fuente externa de su elección.

El primer problema es la contraseña de root de la base de datos; en la versión de desarrollo, está directamente en la URL de la base de datos en el archivo .env, que está registrado en GitHub. Eso no es realmente una buena idea, pero dado que es solo una contraseña de desarrollo, hace que sea muy fácil hacer que un entorno de desarrollo se ejecute en una computadora portátil nueva. Solo afecta a la base de datos en el entorno de desarrollo. GitHub tuvo la amabilidad de enviarme una advertencia de que verifiqué una contraseña en el repositorio.

Tomemos la misma táctica que tomamos con el par certificado / clave y coloquemos los nuevos secretos en el directorio principal donde cloné mi repositorio de GitHub. De esa manera, es fácil de ubicar, pero fuera del repositorio, por lo que no hay posibilidad de que se envíe a GitHub. Quizás podría pensar que deberíamos guardar secretos en un directorio oculto, con solo permisos de root de lectura y escritura. Pero si alguien hackeó su máquina virtual, ninguna protección la mantendrá en secreto. Así que bien podría ser un lugar conveniente. Voy a llamar a mi archivo ../phpapp.env (relativo al directorio del repositorio) y ahora solo tendrá una línea:

MYSQL_ROOT_PASSWORD=<your super secret password here>

Ahora, agreguemos la aplicación MariaDB en  docker-compose.yml.

version: '3.1'
services:
  nginx:
    image: nginx
    restart: always
    volumes:
     - ./nginx.conf:/etc/nginx/nginx.conf
     - ..:/etc/certs
    ports:
     - "443:443"
  mariadb:
    image: mariadb
    restart: always
    env_file:
      - ../phpapp.env
    ports:
      - 3306:3306

Observe que saqué la aplicación de administración en la versión de producción. No quisiera exponer eso al mundo exterior, así que me limitaré a acceder a él a través de la línea de comandos.

Ahora puede abrirlo y confirmar que se está ejecutando

docker-compose up -d
docker exec -it phpappprod_mariadb_1 bash
mariadb -u root -p

Le pedirá la contraseña; puede ingresar la contraseña que puso en el archivo ../phpapp.env y debería ver esto:

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 5
Server version: 10.5.5-MariaDB-1:10.5.5+maria~focal mariadb.org binary distributionCopyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MariaDB [(none)]>

Entonces sabemos que MariaDB está funcionando y la contraseña de root está almacenada de forma segura en ../phpapp.env

El siguiente problema, cualquier dato que se almacene se almacenará dentro del contenedor. Necesitaremos configurar un volumen para almacenar eso externamente. Primero, detenga el monitor MariaDB y luego detenga docker-compose:

docker-compose down

No queremos que los datos de la base de datos se almacenen en GitHub, pero quiero que sea simple y que el directorio de datos esté directamente dentro del área del repositorio. Entonces tendremos que agregar una sola línea al archivo .gitignore:

data

A continuación, crearemos el directorio de datos y le otorgaremos permisos completos para que podamos acceder a él desde dentro de la ventana acoplable como un volumen montado:

mkdir data
chmod 777 data

Finalmente, podemos montarlo en nuestro archivo docker-compose.yml:

...
mariadb:
    image: mariadb
    restart: always
    volumes:
     - ./data:/var/lib/mysql
    env_file:
      - ../phpapp.env
    ports:
      - 3306:3306

Como alternativa, también puede dejar que Docker cree un volumen y lo asigne, pero de esta manera parece más fácil. Si estuviera creando una serie de aplicaciones web que compartieran una base de datos, ese sería el camino a seguir. Pero nos mantendremos simples. Podemos confirmar que MariaDB lo está usando simplemente iniciando y deteniendo los servicios nuevamente:

docker-compose up -d
docker-compose down
ls data

El directorio de data debe estar lleno de archivos. Si alguna vez necesitamos restablecer la base de datos mientras probamos estos scripts de producción localmente, podemos simplemente eliminar el contenido del directorio de datos.

El último problema, crear la base de datos y las tablas para nuestra aplicación, es un poco más complicado. En la primera parte de esta serie, usamos varios comandos PHP / Symfony, pero no queremos instalar PHP o Symfony directamente en nuestra máquina virtual de producción. Recuerde que uno de los objetivos es facilitar el (re) aprovisionamiento, por lo que cuanto menos tengamos que instalar, mejor. Afortunadamente, hay un comando para volcar el SQL necesario y podemos montarlo en un lugar que MariaDB mira cuando no está inicializado. Alternativamente, dado que todos los comandos PHP / Symfony están disponibles en la imagen, puede ejecutar la imagen y anular la línea Docker CMD, pero creo que ejecutar el SQL desde dentro de la imagen MariaDB es un poco más familiar para las personas acostumbradas a ejecutar DDL desde un guión.

También necesitamos anular la URL de la base de datos para la aplicación. Así que agrega una línea a tu archivo ../phpapp.env

DATABASE_URL=mysql://root:<pw>@mariadb:3306/winelist?\   serverVersion=mariadb-10.4.11
MYSQL_ROOT_PASSWORD=<pw>

Reemplace ambos <pw> con su nueva contraseña.

Volviendo al repositorio phpapp, asumiendo que todavía tiene todas sus dependencias descargadas, podemos ejecutar el siguiente comando para obtener el SQL para crear la tabla:

php bin/console doctrine:schema:create --dump-sql

Copie el SQL que genera y vuelva al repositorio  phpappprod, cree un directorio llamado init, y dentro de eso, cree un archivo llamado init.sql y pegue el SQL. Tendremos que modificarlo un poco para crear y usar también la base de datos si no existe. Lo que terminamos con debería verse así:

CREATE DATABASE IF NOT EXISTS winelist;
USE winelist;
CREATE TABLE IF NOT EXISTS wine_pairing 
  (id INT AUTO_INCREMENT NOT NULL, 
   wine VARCHAR(255) NOT NULL, 
   wine_description VARCHAR(255) NOT NULL, 
   cheese VARCHAR(255) NOT NULL, 
   cheese_description VARCHAR(255) NOT NULL, 
   pairing_notes VARCHAR(255) NOT NULL, 
   PRIMARY KEY(id)) 
   DEFAULT CHARACTER SET utf8mb4 
   COLLATE `utf8mb4_unicode_ci`  
   ENGINE = InnoDB;

Ahora agregue el mapeo al docker-compose.yml.

...
mariadb:
    image: mariadb
    restart: always
    volumes:
     - ./data:/var/lib/mysql
     - ./init:/docker-entrypoint-initdb.d
    env_file:
      - ../phpapp.env
    ports:
      - 3306:3306

Para asegurarse de que se inicialice, elimine el contenido del directorio de datos y comience de nuevo. Puede ejecutar la línea de comando MariaDB nuevamente para asegurarse de que todo se haya inicializado:

rm -rf data/*
docker-compose up -d
docker exec -it phpappprod_mariadb_1 bash
mariadb -u root -p

Ejecute un comando describe para ver la nueva tabla  wine_list.

 

Agregar un proxy inverso

Tenemos Nginx y MariaDB funcionando ahora; todo lo que queda es agregar la aplicación y configurar Nginx para que lo invierta. Esto significa otro cambio en el archivo  docker-compose.yml.

...
  phpapp:
    image: rlkamradt/phpapp
    env_file:
      - ../phpapp.env
    ports:
      - 8000:8000

Eso iniciará la aplicación. Configurar Nginx como proxy inverso es bastante sencillo; el nginx.conf debe cambiarse a esto:

events {}
http {
  server {
    listen 443 ssl;
    server_name kamradtfamily.net;
    ssl_certificate /etc/certs/kamradtfamily.net.pem;
    ssl_certificate_key /etc/certs/kamradtfamily.net.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    location / {
        proxy_pass http://phpapp:8000;
    }
  }
}

Esto tomará todas las solicitudes y las pasará a la aplicación. Tenga en cuenta que no es necesario que habilite HTTPS en la aplicación en sí, ya que la comunicación entre Nginx y la aplicación se lleva a cabo por completo en una sola máquina virtual, por lo que es seguro y eficiente usar la comunicación no segura entre ellos.

Hacer que todo esto llegue al servidor requerirá solo una pequeña cantidad de esfuerzo. Primero, asegúrese de haber confirmado y enviado todos los cambios en el repositorio phpappprod. Luego, copie su ../phpapp.env en su VM en su directorio de inicio, que debería ser el directorio principal del repositorio en la VM. La forma más sencilla de hacer esto es simplemente iniciar sesión en su VM, ejecutar vi phpapp.env y cortar y pegar su copia local, de manera similar a como obtuvimos los certificados en la VM. Finalmente, en la VM, cambie al directorio phpappprod y haga un git pull para obtener todos los cambios que hicimos localmente. ¡Ahora puede ejecutar docker-compose up -d y su aplicación debería estar lista para publicarse en Internet en vivo!

Nginx

Agregamos algunos elementos a la lista para que pueda ver. Todavía se ve bastante feo, pero un poco de estilo lo arreglará.

Pero primero, cerraré el servidor. Estoy seguro de que, si lo dejo, cuando regrese, algún bromista lo habrá llenado de cosas graciosas, o algo peor. ¡Nunca deje una aplicación a medio terminar en vivo en Internet!

¿Qué queda por hacer? Además de hacer que se vea bonito, necesitamos agregar un inicio de sesión para que solo las personas autorizadas puedan agregar emparejamientos. Una vez que tengamos un inicio de sesión, me sentiré bastante seguro de dejar esto funcionando en Internet. Y una vez que esté arreglado, me sentiré bien por tenerlo como un artículo de cartera. He usado Okta antes para la autorización, por lo que en mi próxima publicación, mostraré cómo usarlos para iniciar sesión en su aplicación.

Estos son los repositorios utilizados en este artículo:

 

https://github.com/rkamradt/phpapp/tree/v0.2

 

https://github.com/rkamradt/phpappprod/tree/v0.1

Recent Post