Deploy di un’Applicazione Laravel 5.6 con ECS + Fargate

Ultimamente mi ritrovo spesso a giocare con AWS. In primis perché sto studiando per la certificazione come Associate Developer, certo, ma anche perché sto scoprendo una marea di roba nuova che mi facilita (enormemente) la vita.

Nel frattempo, ho iniziato a lavorare ad un side-project. Non per studiare qualcosa di nuovo ma proprio per “lanciare” qualcosa di mio. Ho deciso così di scegliere una tecnologia che conosco già molto bene e ho scelto Laravel. Per quanto riguarda lo sviluppo in locale non c’è problema: Vessel di ShippingDocker ha tutto quello di cui ho bisogno (per i più pigri, sappiate che con il mio caro e vecchio Laraprep tiro su tutto il dev env in due minuti).

Rimane quindi una sola domanda: cosa scelgo per l’ambiente di produzione?

Tendenzialmente per i side-project vado di deploy su Forge. Approccio quick and dirty e ho tutto quello che mi serve in tempo zero.

Che gusto c’è, però, se non imparo nulla di nuovo?

Ne ho parlato con un mio collega, Eraclitux, che per queste cose è sempre molto sul pezzo. Anche stavolta non mi ha deluso. Gli ho detto che mi sarebbe piaciuto giocare con i container anche in produzione e non solo in locale. La sua risposta è arrivata subito:

“Prova ECS + Fargate.”

Far-che?

Ok, facciamo un po’ d’ordine. Non voglio entrare nel dettaglio, mi tengo sulle basi.

Fargate è fondamentalmente una tecnologia messa a disposizione da AWS per far girare servizi usando i container senza doversi preoccupare di “tutto quello che c’è sotto”. Non bisogna più stare lì a configurare le istanze EC2, eseguire il provision, ridimensionarle e così via.

Essendo tutto più “astratto” il beneficio è il caro e vecchio “potersi concentrare sulla propria applicazione”. Il funzionamento è piuttosto semplice:

  • si crea la build della docker image per l’applicazione che si vuole mandare su;
  • la build viene pushata su Docker Hub (solo public, non private) o su ECR (Elastic Container Registry);
  • si definisce un task che descrive il cluster per il servizio che stiamo rilasciando;
  • il task viene eseguito  e il cluster con il nostro servizio viene rilasciato;

L’obiettivo del mio esperimento era mandare su un’applicazione Laravel e vederla girare correttamente.

In questo articolo spiego come ho fatto.

Primo passo: il boilerplate

L’argomento per me è nuovo: la prima cosa che ho fatto è stata cercare in giro quanti più riferimenti possibili. Prendendo spunto da vari repo su Github (e qualche articolo su Medium) mi sono fatto una prima idea: per comdità, è decisamente più utile avere un boilerplate di partenza che contenga tutto quello che raramente cambierò.

Questo boilerplate non è altro che un’immagine Docker, di cui è possibile trovare i file qui.

Guardiamo un attimo il Dockerfile:

[bash]
FROM ubuntu:xenial

RUN rm /bin/sh && ln -s /bin/bash /bin/sh

RUN apt-get update

RUN apt-get -y install \
software-properties-common \
python-software-properties

RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php

RUN apt-get update

RUN apt-get -y install \
git \
curl \
zip \
wget \
unzip \
php7.1 \
php7.1-xml \
php7.1-mbstring \
php7.1-mysql \
php7.1-gd \
php7.1-curl \
php7.1-redis \
php7.1-fpm \
php7.1-zip

RUN wget http://nginx.org/keys/nginx_signing.key

RUN apt-key add nginx_signing.key

RUN rm -rf nginx_signing.key

RUN echo ‘deb http://nginx.org/packages/ubuntu/ ‘$(lsb_release -cs)’ nginx’ > /etc/apt/sources.list.d/nginx.list

RUN apt-get update && apt-get -y install nginx

RUN apt-get clean

RUN curl -sS https://getcomposer.org/installer | php — –install-dir=/usr/local/bin –filename=composer

RUN mkdir -p /app && rm -fr /usr/share/nginx/html && ln -s /app /usr/share/nginx/html

RUN rm -rf /etc/nginx/conf.d/default.conf
ADD nginx/default.conf /etc/nginx/conf.d/default.conf
ADD php/www.conf /etc/php/7.1/fpm/pool.d/www.conf

RUN sed -i "s/user nginx;/user www-data;/g" /etc/nginx/nginx.conf

RUN mkdir /var/run/php

ADD run.sh /run.sh
RUN chmod 755 /run.sh

WORKDIR /app
RUN chmod -R 777 /app

ONBUILD ADD app /app
ONBUILD RUN composer install –no-dev
ONBUILD RUN chown www-data:www-data /app -R

EXPOSE 80

CMD ["/run.sh"]
[/bash]

Non stiamo facendo nulla di davvero complesso. Chiaramente ci sono un bel po’ di cose che possono essere migliorate, specialmente in termini di sicurezza, ma il mio obiettivo oggi è mandare il tutto su ECS, quindi sorvolerò.

Da notare le ultime istruzioni, le ONBUILD. Le ho scoperte proprio in occasione di questo esperimento. In poche parole, permettono di specificare delle istruzioni che verranno eseguite al build di un’immagine che usa questa attuale come “base”.

Esempio:

  • sto creando questa immagine boilerplate che contiene PHP, Nginx e qualche dipendenza. La chiameremo immagine A;
  • l’immagine della mia applicazione avrà come base questo boilerplate. La chiameremo B;
  • tutte le istruzioni precedute da ONBUILD, anche se specificate nel dockerfile di A, verranno eseguite al build di B!

Ok, proviamoci insieme. Iniziamo:

[bash]docker build -t laravel-boilerplate .[/bash]

Aspettiamo qualche minuto ed il nostro boilerplate è pronto. Step 1 completato!

Build dell’applicazione

Quello che dobbiamo fare, adesso, è creare una nuova immagine basata su laravel-boilerplate.

Usciamo dalla directory attuale e creiamone un’altra, che chiameremo laravel-app. In questa cartella avremo soltanto:

  • una cartella app che conterrà il codice della nostra applicazione;
  • un file Dockerfile molto, molto sintetico;

[bash]FROM laravel-boilerplate[/bash]

A questo punto, non rimane che provare ad avviare in locale la nostra applicazione, eseguendo un singolo comando:

[bash]docker run -ti -p 80:80 -e APP_KEY="base64:qfZ1tP3wnDR8hLHuxHLPo6Ksx1LQXPMA9P088ym9zdg=" laravel-app[/bash]

Et voilà! Proviamo a visitare localhost e…

Funziona!

Il Gran Finale… Deploy!

Ora che abbiamo la nostra immagine, per arrivare ad usare ECS e Fargate non rimane che seguire gli ultimi due step:

  1. Push dell’immagine su AWS ECR;
  2. Usare l’immagine mandata su ECR per creare un nuovo Task;

Push dell’Immagine

Questa è piuttosto semplice. Basta fare così:

  1. entrare nella console di AWS;
  2. tra i vari servizi, scegliere ECS;
  3. una volta dentro, scegliere “Repositories” da “Amazon ECR”;
  4. scegliere “Create repository” e dare un nome al nuovo repository (io ho optato per un originalissimo laravel-app);

Verranno mostrate una serie di istruzioni da seguire e di comandi da lanciare. Leggiamo tutto e seguiamo le istruzioni alla lettera. Una volta lanciato l’ultimo comando quello di push, aspettiamo qualche minuto. Il risultato sarà ritrovarci la nostra immagine su ECR, pronta ad essere usata!

Deploy… davvero!

Non rimane che creare il cluster ed il task che verrà eseguito sul cluster in questione.

Sempre dal menu di ECS, scegliamo “Create Cluster” dal menu “Cluster”. Io l’ho chiamato my-laravel-app. Confermiamo la creazione.

Adesso passiamo al task. Scegliamo “Create task definition” dal menu “Task definitions”. Andiamoci pure tranquilli con i requisiti:

Adesso viene il bello. Nel contesto del task dobbiamo specificare i container che useremo. Ne abbiamo solo uno: quello che creeremo dall’immagine appena mandata su.

L’url da usare per l’immagine viene fornito da ECR stesso dopo aver effettuato il push. Qui nell’immagine non si vede, ma ho aggiunto anche il port mapping sulla 80. Non scordiamoci inoltre di impostare la variabile d’ambiente APP_KEY, altrimenti l’applicazione Laravel restituirà un errore.

… è fatta!

Non rimane che avviare il tutto. Dall’elenco dei task, scegliamo il nostro e clicchiamo su “Actions”, quindi “Run task”.

Ci si aprirà una schermata in cui scegliere pochi ultimi dettagli, tra cui:

  • la VPC da usare per il deploy;
  • la subnet di riferimento;
  • la generazione (o meno) di un public ip address (enabled, mi raccomando…);

Confermiamo ed aspettiamo un po’… inizialmente il task sarà in PENDING, una volta terminata la procedura passerà in RUNNING.

Prendiamo l’IP pubblico che ci viene restituito dalla console e raggiungiamo la nostra applicazione.

Funziona?

Funziona.

Missione completata!

Conclusioni

Difficile trovare delle conclusioni non banali: non c’è bisogno di questo blog per capire la portata di uno strumento del genere.

In linea di massima posso dire che:

  • è una figata ed è stato semplicissimo da usare anche per me che di Docker/Container non sono espertissimo ma me la cavo soltanto;
  • bad boy AWS per l’impossibilità di usare immagini private su Docker Hub;
  • una volta finita l’app a cui sto lavorando penso proprio mi divertirò a mettere su un bel cluster con l’aggiunta di un bel load balancer e qualche regoletta per l’autoscaling;

Insomma, ci si vede presto 🙂