AWS e Docker Cloudstor

di Andrea Zani, in AWS,

Questo post dovrebbe essere un seguito di questo e quest'altro post. Dopo aver progettato un cluster adatto alle nostre esigenze con Docker Swarm che includa macchine idonee per i container che dovranno ospitare - per esempio, macchine con l'accesso alla rete più veloce per le web application, macchine per i database con determinati tipi di dischi ecc... - e avere creato il tutto all'interno di AWS, è arrivato il momento di fare sul serio e installare e configurare le risorse che saranno poi utilizzate. Prendendo spunto proprio dall'esempio del database, si dovrà fare in modo che la macchina su cui girerà, salvi i dati su un disco reale, e non all'interno dei volume di Docker.

AWS utilizza dei volumi EBS come dischi virtuali per le sue macchina virtuali EC2. Una volta creata una macchina virtuale viene creato un volume EBS e collegato ad essa, che sarà utilizzato per la memorizzazione del sistema operativo e dei dati dell'utente. Una volta terminata questa macchina virtuale anche il volume EBS viene cancellato automaticamente. Si possono creare ulteriori volumi ESB e attaccarli alle istanze EC2, e questi, dopo essere stati montati correttamente, saranno visti e saranno utilizzabili come il volume principale, ma avranno il vantaggio di NON essere distrutti insieme alla macchina virtuale in caso di voluta chiusura della stessa o per cause esterne (crash o altro). Inoltre si possono creare snapshot di questi volumi per avere una ulteriore sicurezza per evitare perdita di dati e per un ripristino veloce di una macchina in caso di necessità.

Questi volumi esterni sono una buona soluzione per l'archiviazione dei dati di un database (ma potrebbero essere usati anche per salvare altri tipo di file utilizzati dalle nostre applicazioni, o per la gestione dei file statici per una pagina HTML, ecc...), e per facilitare il loro utilizzo Docker ha creato un plugin apposito da utilizzare in AWS (ma pure in Azure): Cloudstor (qui la versione per Azure). Questo plugin crea automaticamente un volume EBS e lo collega direttamente ad una istanza EC2; inoltre, è in grado di scollegare automaticamente il volume EBS da una macchina per ricollegarla ad un'altra. Se le due macchine virtuali sono nella stessa zona, questo plugin scollega il volume dalla prima macchina e lo ricollega alla seconda macchina, ma se le due macchine sono in due zone diverse, il plugin scollega il volume, crea uno snaphost del disco, lo copia e crea un nuovo volume nella zona della seconda macchina ricollegandolo, e infine cancella il volume dalla zona precedente, tutto in modo automatico. Inoltre, questo plugin ha una comodissima feature: ogni cinque minuti crea uno snaphost del volume (cancellando le copie più vecchie) in modo che da poter recuperare il suo contenuto in caso di problemi o errori personali.

Mostro ora un esempio reale di questo plugin. Per semplicità userò una semplice web application che legge e visualizza in una pagina HTML il contenuto del file salvato su disco. Nella prima parte dei miei post dedicati ad AWS, avevo utilizzato uno script in Python con Troposphere per creare il file YML per la creazione delle macchine e del cluster con Docker Swarm. Per potere utilizzare Cloudstor sono necessari altri permessi che in quello script non sono presenti; qui ho creato un nuovo branch apposito con le modifiche. In AWS per dare i permessi alle macchina virtuali di poter creare ed accedere ai volumi EBS con Cloudstor sono necessari questi permessi:

"Action": [
      "ec2:CreateTags",
      "ec2:AttachVolume",
      "ec2:DetachVolume",
      "ec2:CreateVolume",
      "ec2:DeleteVolume",
      "ec2:DescribeVolumes",
      "ec2:DescribeVolumeStatus",
      "ec2:CreateSnapshot",
      "ec2:DeleteSnapshot",
      "ec2:DescribeSnapshots"
    ],
    "Effect": "Allow",
    "Resource": "*"

Riprendendo il mano il file troposphere_create.py:

policies.append(Policy(
            PolicyName=environmentString + "CloudStor",
            PolicyDocument=awacs.aws.Policy(
                Statement=[
                    Statement(
                        Effect=Allow,
                        Action=[
                            Action("ec2", "CreateTags"),
                            Action("ec2", "AttachVolume"),
                            Action("ec2", "DetachVolume"),
                            Action("ec2", "CreateVolume"),
                            Action("ec2", "DeleteVolume"),
                            Action("ec2", "DescribeVolumes"),
                            Action("ec2", "DescribeVolumeStatus"),
                            Action("ec2", "CreateSnapshot"),
                            Action("ec2", "DeleteSnapshot"),
                            Action("ec2", "DescribeSnapshots")
                        ],
                        Resource=["*"]
                    )
                ]
            )
        )
    )

Inoltre, essendo le macchine create dal mio script completamente customizzate per le installazioni, il plugin Cloudstor dev'essere installato manualmente (il plugin Cloudstor nelle macchine virtuali create automaticamente da ECS di AWS è già installato di default):

AwsRegion=$(curl -s 169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/.$//')
...
docker plugin install --alias cloudstor:aws --grant-all-permissions docker4x/cloudstor:18.09.2-ce-aws1     CLOUD_PLATFORM=AWS AWS_REGION=${AwsRegion} EFS_SUPPORTED=0 DEBUG=0

In futuro potrebbero uscire ulteriori versioni di questo plugin ma io ho inserito la versione direttamente: verificare in caso la versione idonea alla versione di Docker utilizzata.

Eseguito questo script, l'output sarà utilizzabile in CloudFormation di AWS per la creazione del tutto:

Dopo pochi minuti saranno create le macchine e configurato il cluster in Docker Swarm (ma per i dettagli rimando ai miei due post precedenti). Un controllo nella console di AWS:

Nella lista, la seconda è la macchina master per la gestione del cluster Docker Swarm, e la prima nella lista è una macchina worker e sono state installate in due zone separate (eu-central-1a e eu-central-1c), ottimo per testare il corretto funzionamento di Cloudstor. E ora è il momento di provare questo plugin. Ad aiutarmi una web application che mi ero scritto per un semplice test e pubblicata come immagine dell'hub pubblico di Docker:

https://hub.docker.com/r/sbraer/aspnetappweb

Si basa sull'esempio di base presente in Visual Studio al quale è stato aggiunto un tab (AZ Custom page) il cui contenuto viene letto da un file di testo.

Il file letto ha il nome msg.txt ed è inserito nel path /efs (ho  utilizzato questo nome per la directory perché lo utilizzerò anche nell'esempio dedicato ad EFS di cui scriverò dopo).

Per fare il deploy in docker si usa ormai da sempre Docker Compose, ma nel caso di Docker Swarm si deve usare Docker Stack. Mi collego con un terminale alla macchina master principale (per sapere l'ip si può controllare direttamente nella lista delle istanze EC2 o nel tab di output on CloudFormation) e inserire il file YML per la configurazione della mia web app e di Cloudstor:

version: '3.7'

# disk extern
volumes:
  data:
    driver: cloudstor:aws
    driver_opts:
      backing: relocatable
      size: 1
      ebstype: gp2

# Define services
services:
  # App Service
  app:
    # Configuration for building the docker image for the service
    image: sbraer/aspnetappweb
    ports:
      - "5000:5000" # Forward the exposed port 8080 on the container to port 8080 on the host machine
    deploy:
      replicas: 1
      placement: 
        constraints: 
          - node.role == manager #worker 
      restart_policy:
        condition: on-failure  
    networks: # Networks to join (Services on the same network can communicate with each other using their name)
      - webnet
    volumes:
      - data:/efs

networks:
  webnet:
    driver: overlay
    attachable: true 
 

Il blocco volumes dichiara la creazione di un nuovo volume utilizzando il pluigin Cloudstor. Con relocatable si specifica che il volume è di tipo ESB (altra opzione è shared, ma è da utilizzare solo se si vuole che Cloudstor gestisca i dischi EFS). Il size specifica la grandezza in GB (in questo caso solo 1GB) e in ESBType si specifica il tipo di disco, gp2 è il disco SSD di base, oppure: io1, st1, sc1 - dettagli qui.

In services ho definito la mia app: il nome dell'immagine di Docker, le porte da esporre del container che sarà creato, il constraints per specificare che il container dovrà essere installato e avviato su una macchina manager nel cluster di Docker Swarm, la rete network da utilizzare e infine il volume da montare: disco ESB gestito da Cloudstor sata sarà montato come /efs.

E' ora giunto il momento di avviare il tutto:

< docker stack deploy -c docker-stack.yml swarm-stack
> Creating network swarm-stack_webnet
> Creating service swarm-stack_app

E ora controllo che il servizio sia partito:

< docker service ls
> ID                  NAME                MODE                REPLICAS            IMAGE                        PORTS
> lr1ya2xch39q        swarm-stack_app     replicated          0/1                 sbraer/aspnetappweb:latest   *:5000->5000/tcp

La procedura non è velocissima, ma si può controllare lo stato con questi comandi:

docker service logs swarm-stack_app
docker stack ps swarm-stack

Se controllo nella console di AWS i volumi presenti ne troverò uno nuovo con la dimensione corretta di 1GB, montato nella stessa zone della macchina virtuale master eu-central-1a:

Come scritto precedentemente, Cloudstor crea automaticamente degli snapshot automatici del disco da lui gestito ogni cinque minuti. Se vado nella sezione snapshot di AWS (in cui trovo anche la copia in corso dopo esattamente cinque minuti):

Torno al terminale e controllo che replicated sia 1/1, e infine testo il tutto da browser, prendendo l'ip pubblico della macchina creata in AWS:

Se tutto funziona è arrivato il momento di cliccare sul link dell'ultimo tab "AZ Custom page":

Come mi aspettavo: il file non è stato trovato (il disco è ancora vuoto). Per creare il file devo collegarmi direttamente al container. Come da constraint, dovrei trovarlo nella macchina virtuale master da cui sto facendo tutte queste prove:

Dopo il comando docker exec posso inserire direttamente i comandi nel container dove gira la mia web application:

echo "Hello World!" > /efs/msg.txt

Faccio il refresh della pagina nel browser:

E' il momento di controllare se Cloudstor funziona correttamente anche per il recupero di questo disco. Riprendo in mano il file docker-stack.yml e modifico il constraint: da manager a worker:

- node.role == worker

E aggiorno lo stack:

< docker stack deploy -c docker-stack.yml swarm-stack
> Updating service swarm-stack_app (id: lr1ya2xch39qqghpumkmt76as)

In AWS vedo tra i volume la disconnessione del disco ESB da 1GB (da in use a available):

Dopo pochi minuti ecco che appare un nuovo disco da 1GB ma non più nella zona eu-central-1a dove gira la macchina virtuale master, ma nella zona eu-central-1c dove gira la macchina worker:

Inoltre il container è stato spostato da Docker Swarm sulla macchina worker e la richiesta la pagina nel browser continua a funzionare:

Controllando quello che è successo internamente con docker stack ps swarm-stack si vede come il vecchio container sulla macchina master è stato spento ed è stato riavviato sulla macchina worker.

Con Cloudstor è possibile anche utilizzare volumi condivisi grazie a EFS. Anche se c'è un metodo più tradizionale per farlo, l'uso di questo plugin è comodo e facilita le cose. Innanzitutto è necessario installare il plugin con la modalità EFS attiva:

docker plugin install --alias cloudstor:aws --grant-all-permissions docker4x/cloudstor:18.09.2-ce-aws1 \
 CLOUD_PLATFORM=AWS EFS_ID_REGULAR={id-efs} EFS_ID_MAXIO={id-efs} \ 
 AWS_REGION={region} EFS_SUPPORTED=1 DEBUG=1

Il parametro {id-efs} è l'id univoco della risorsa in AWS, per esempio fs-03e91z5b; tale valore può essere ripetuto anche nel parametro EFS_ID_MAXIO (un bug di una versione precedente di Cloudstor impediva l'inserimento della stessa risorsa EFS e si doveva creare sempre due risorse anche se una non veniva utilizzata); {regione}, infine, è la regione dove è stata inserita la risorsa, per esempio eu-central-1 per Francoforte. A questo link ho inserito un branch per la creazione automatica del tutto con Troposphere:

https://github.com/sbraer/AwsNodeJsCodeDeploy/tree/cloudstor-efs

Ora alla creazione delle risorse con CloudFormation, sarà creato una risorsa EFS nelle macchine EC2 in AWS sarà installato il plugin con il supporto a EFS con l'inserimento corretto dell'id e della regione.

Modifico il file per la creazione dello stack (il tutto si limita ad una linea di codice: backing: shared):

version: '3.7'

# disk extern
volumes:
  data:
    driver: cloudstor:aws
    driver_opts:
      backing: shared

# Define services
services:
  # App Service
  app:
    # Configuration for building the docker image for the service
    image: sbraer/aspnetappweb
    ports:
      - "5000:5000" # Forward the exposed port 8080 on the container to port 8080 on the host machine
    deploy:
      replicas: 1
      placement: 
        constraints: 
          - node.role == manager #worker 
      restart_policy:
        condition: on-failure  
    networks: # Networks to join (Services on the same network can communicate with each other using their name)
      - webnet
    volumes:
      - data:/efs

networks:
  webnet:
    driver: overlay
    attachable: true 
 

Lancio il tutto:

    docker stack deploy -c docker-stack.yml swarm-stack

E aspetto che tutto funzioni. Richiamato con il browser la pagina otterrò l'errore visto prima perché il file msg.txt non è stato trovato. Devo fare come prima: entrare nella macchina, ma senza bisogno di scomodare Docker, posso montare il disco EFS anche direttamente. Preso nota del DNS Name della risorsa EFS da utilizzare (visibile dalla console di AWS) e lancio questi comandi:

mkdir /efs
mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport  fs-14d23d1c.efs.eu-central-1.amazonaws.com:/ /efs

Se tutto è andato bene non si otterranno errori (se si sta provando lo stesso codice, modificare il nome delle risorse). Quindi controllo in contenuto:

cd /efs
ls

Che restituirà il nome di una directory:

swarm-stack_data

Il nome di questa directory è automatico e deriva dal nome dello stack e dal nome della directory definita nel file docker-stack.yml. Inserito qui il file msg.txt come visto prima e aggiornata la pagina del browser, sarà visualizzato il suo contenuto.

E' possibile creare anche più directory in EFS. Prendendo l'esempio di prima:

# disk extern
volumes:
  data:
    driver: cloudstor:aws
    driver_opts:
      backing: shared
  images:
    driver: cloudstor:aws
    driver_opts:
      backing: shared

Troverò due directory: swarm-stack_data e swarm-stack_images.

Conclusioni: l'unico problema che ho trovato con questo plugin è che, quando si decide di distruggere lo stack e tutte le risorse utilizzate in AWS grazie a CloudFormation,  dischi ESB e le immagini snapshot create non sono cancellate automaticamente ma si deve sempre procedere alla loro eliminazione manualmente, problema che con EFS non c'è.

Può essere utile questo post... a me.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese