Docker Run: Come creare immagini da un’applicazione
Nella nostra precedente serie di blog, abbiamo visto come implementare Kubernetes e creare un cluster. Abbiamo anche visto come distribuire un’applicazione nell’ambiente del cluster e configurare le istanze OpenStack (inclusa la sicurezza) per aiutarvi ad accedervi. Ora andremo più a fondo nello sviluppo di Kubernetes guardando come creare immagini Docker in modo da poter distribuire le proprie applicazioni e renderle disponibili ad altre persone.
Come funzionano le immagini Docker
Prima di imparare a creare immagini Docker, la prima cosa che dobbiamo capire è come funzionano le immagini Docker stesse.
La chiave di un’immagine Docker è che è un file system a strati. In altre parole, se si inizia con un’immagine che è solo il sistema operativo (diciamo Windows) e poi si aggiunge un’applicazione (diciamo Nginx), si finirà con qualcosa come questo:
Come potete vedere, la differenza tra IMAGE1 e IMAGE2 è solo l’applicazione stessa, e poi IMAGE4 ha le modifiche fatte sui livelli 3 e 4. Quindi, per creare un’immagine, stai fondamentalmente partendo da un’immagine di base e definendo le modifiche ad essa.
Ora, ti sento chiedere: “Ma se volessi partire da zero?” Bene, definiamo “da zero” per un minuto. È probabile che intendiate dire che volete iniziare con un sistema operativo pulito e partire da lì. Bene, nella maggior parte dei casi c’è un’immagine di base per questo, quindi state ancora iniziando con un’immagine di base. (In caso contrario, puoi controllare le istruzioni per creare un’immagine base Docker.)
In generale, ci sono due modi per creare una nuova immagine Docker:
- Crea un’immagine Docker da un contenitore esistente: In questo caso, si parte da un’immagine esistente, la si personalizza con le modifiche che si desidera, quindi si costruisce una nuova immagine da essa.
- Usare un Dockerfile: In questo caso, si usa un file di istruzioni – il Dockerfile – per specificare l’immagine di base e le modifiche che si vogliono apportare ad essa.
In questo articolo, vedremo entrambi questi metodi. Iniziamo con la creazione di una nuova immagine da un contenitore esistente.
Creare da un contenitore esistente
Quando si tratta di Docker, iniziare può essere abbastanza semplice. In questo esempio, inizieremo con un’immagine che include il server di applicazioni web nginx e PHP. A questo, aggiungeremo il supporto per la lettura dei dati RSS utilizzando un pacchetto open-source chiamato SimplePie (disponibile per il download su GitHub). Faremo quindi una nuova immagine dal contenitore modificato.
Creare il contenitore Docker originale
La prima cosa che dobbiamo fare è istanziare l’immagine base originale, o far creare a docker un contenitore da un’immagine.
- Il primo passo è assicurarsi che il sistema abbia Docker installato. Se avete seguito la nostra serie precedente sull’esecuzione di Kubernetes su OpenStack, avete già gestito la cosa. In caso contrario, potete seguire le istruzioni qui per distribuire Docker.
- Poi, avrete bisogno di ottenere l’immagine di base. Nel caso di questo tutorial, si tratta di webdevops/php-nginx, che fa parte di Docker Hub, quindi per poterla “tirare” dovrete avere un ID Docker Hub. Se non ne avete già uno, andate su https://hub.docker.com e create un account gratuito.
- Vai alla riga di comando dove hai installato Docker e accedi a Docker Hub:
# docker loginLogin con il tuo ID Docker per spingere e tirare immagini da Docker Hub. Se non avete un ID Docker, andate su https://hub.docker.com per crearne uno.Username: nickchasePassword:Login Succeeded
- Inizieremo con l’immagine base e faremo avviare un contenitore da Docker. Istanziare webdevops/php-nginx:
# docker run -dP webdevops/php-nginx
Il flag -dP fa sì che il contenitore venga eseguito in background, e che le porte su cui ascolta siano rese disponibili.
- Assicurati che il contenitore sia in esecuzione:
# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1311034ca7dc webdevops/php-nginx "/opt/docker/bin/entr" 35 seconds ago Up 34 seconds 0.0.0.0:32822->80/tcp, 0.0.0.0:32821->443/tcp, 0.0.0.0.0:32820->9000/tcp small_bassi
Un paio di note qui. Prima di tutto, poiché non abbiamo specificato un nome particolare per il contenitore, Docker ne ha assegnato uno. In questo esempio, è small_bassi. In secondo luogo, si noti che ci sono 3 porte aperte: 80, 443, e 9000, e che sono state mappate su altre porte (in questo caso 32822, 32821, e 32820, rispettivamente – sulla tua macchina queste porte saranno diverse). Questo rende possibile che più contenitori siano “in ascolto” sulla stessa porta sulla stessa macchina host. Quindi, se dovessimo provare ad accedere ad una pagina web ospitata da questo contenitore, lo faremmo accedendo a:
http://localhost:32822
Finora, però, non ci sono pagine a cui accedere; rimediamo.
Creare un file sul contenitore
Per poter testare questo contenitore, dobbiamo creare un file PHP di esempio. Lo faremo accedendo al contenitore e creando un file.
- Accedere al contenitore
# docker exec -it small_bassi /bin/bashroot@1311034ca7dc:/#
Usando exec con lo switch -it si crea una sessione interattiva per eseguire comandi direttamente nel contenitore. In questo caso, stiamo eseguendo /bin/bash, quindi possiamo fare qualsiasi altra cosa di cui abbiamo bisogno.
- La root del documento per il server nginx in questo contenitore è in /app, quindi vai avanti e crea il file /app/index.php:
vi /app/index.php
- Aggiungere una semplice routine PHP al file e salvarla:
<?phpfor ($i; $i < 10; $i++){ echo "Numero articolo ".$i."\n";}?>
- Ora uscite dal contenitore per tornare alla linea di comando principale:
root@1311034ca7dc:/# exit
- Ora testiamo la pagina. Per farlo, eseguite un semplice comando curl:
# curl http://localhost:32822/index.phpNumero articoloNumero articolo 1Numero articolo 2Numero articolo 3Numero articolo 4Numero articolo 5Numero articolo 6Numero articolo 7Numero articolo 8Numero articolo 9
Ora è il momento di andare avanti e aggiungere RSS.
Apportare modifiche al contenitore
Ora che sappiamo che il PHP funziona possiamo andare avanti e aggiungere il supporto RSS usando il pacchetto SimplePie. Per farlo, lo scaricheremo semplicemente nel contenitore e lo installeremo.
- Il primo passo è quello di accedere nuovamente al contenitore:
# docker exec -it small_bassi /bin/bashroot@1311034ca7dc:/#
- Poi vai avanti e usa curl per scaricare il pacchetto, salvandolo come file zip:
root@1311034ca7dc:/# curl https://codeload.github.com/simplepie/simplepie/zip/1.4.3> simplepie1.4.3.zip
- Ora è necessario installarlo. Per farlo, decomprimere il pacchetto, creare le directory appropriate e copiare i file necessari come segue:
root@1311034ca7dc:/# unzip simplepie1.4.3.ziproot@1311034ca7dc:/# mkdir /app/phproot@1311034ca7dc:/# mkdir /app/cacheroot@1311034ca7dc:/# mkdir /app/php/libraryroot@1311034ca7dc:/# cp -r s*/library/* /app/php/library/.root@1311034ca7dc:/# cp s*/autoloader.php /app/php/.root@1311034ca7dc:/# chmod 777 /app/cache
- Ora abbiamo solo bisogno di una pagina di prova per essere sicuri che funzioni. Create un nuovo file nella directory /app:
root@1311034ca7dc:/# vi /app/rss.php
- Ora aggiungete il contenuto del file di esempio. (Questo file è estratto dal sito web di SimplePie, ma l’ho tagliato per brevità, dato che non è proprio l’obiettivo di quello che stiamo facendo. Si prega di vedere la versione originale per commenti, ecc.)
<?phprequire_once('php/autoloader.php');$feed = new SimplePie();$feed->set_feed_url("http://rss.cnn.com/rss/edition.rss");$feed->init();$feed->handle_content_type();?><html><head><title> Pagina SimplePie di esempio</title></head><body><div class="header"><h1><a href="<?php echo $feed->get_permalink(); ?>><?php echo $feed->get_title(); ?></a></h1><p><?php echo $feed->get_description(); ?></p></div><?php foreach ($feed->get_items() as $item): ?><div class="item"><h2><a href="<?php echo $item->get_permalink(); ?>><?php echo $item->get_title(); ?></a></h2><p><?php echo $item->get_description(); ?></p><p><small>Postata su <?php echo $item->get_date('j F Y | g:i a'); ?></small></p></div><?php endforeach; ?></body></html>
- Esci dal contenitore:
root@1311034ca7dc:/# exit
- Assicuriamoci che funzioni. Ricordate, dobbiamo accedere al contenitore sulla porta alternativa (controllate docker ps per vedere quali porte dovete usare):
# curl http://localhost:32822/rss.php<html><head><title> Pagina SimplePie di esempio</title></head><body><div class="header"><h1><a href="http://www.cnn.com/intl_index.html">CNN.com - Canale RSS - Intl Homepage - News</a></h1><p>CNN.com fornisce notizie e informazioni aggiornate al minuto sulle ultime notizie di attualità, meteo, intrattenimento, politica e altro ancora.</p></div>...
Ora, possiamo trasformarlo in una nuova immagine.
Creare la nuova immagine
Ora vediamo come creare un’immagine Docker dal contenitore. Abbiamo un container funzionante e vogliamo trasformarlo in un’immagine e spingerlo su Docker Hub in modo che gli utenti della comunità possano accedervi. Il nome che userete per il vostro contenitore avrà tipicamente tre parti:
/:
Per esempio, il mio nome utente Docker Hub è nickchase, quindi nominerò la versione 1 del mio nuovo contenitore RSS-ified
nickchase/rss-php-nginx:v1
- Se quando abbiamo iniziato a parlare delle differenze tra i livelli avete iniziato a pensare ai sistemi di controllo delle versioni, avete ragione. Il primo passo nella creazione di una nuova immagine è quello di impegnare le modifiche che abbiamo già fatto, aggiungendo un messaggio sulle modifiche e specificando l’autore, come in:
docker commit -m "Message" -a "Author Name"
Quindi nel mio caso, sarà:
# docker commit -m "Added RSS" -a "Nick Chase" small_bassi nickchase/rss-php-nginx:v1sha256:148f1dbceb292b38b40ae6cb7f12f096acf95d85bb3ead40e07d6b1621ad529e
- Poi vogliamo andare avanti e spingere la nuova immagine al Docker Hub in modo da poterla utilizzare:
# docker push nickchase/rss-php-nginx:v1Il push si riferisce ad un repository 69671563c949: Pushed3e78222b8621: Pushed5b33e5939134: Pushed54798bfbf935: Pushedb8c21f8faea9: Pushed...v1: digest: sha256:48da56a77fe4ecff4917121365d8e0ce615ebbdfe31f48a996255f5592894e2b size: 3667
- Ora se si elencano le immagini che sono disponibili, si dovrebbe vedere nella lista:
# docker imagesREPOSITORY TAG IMAGE ID CREATO SIZEnickchase/rss-php-nginx v1 148f1dbceb29 11 minuti fa 677 MBnginx latest abf312888d13 3 giorni fa 181.5 MBwebdevops/php-nginx più recente 93037e4c8998 3 giorni fa 675.4 MBubuntu più recente e4415b714b62 2 settimane fa 128.1 MBhello-world più recente c54a2cc56cbb 5 mesi fa 1.848 kB
- Ora andiamo avanti e testiamolo. Inizieremo fermando il contenitore originale in modo da poter rimuovere la copia locale dell’immagine:
# docker stop small_bassi# docker rm small_bassi
- Ora possiamo rimuovere l’immagine stessa:
# docker rmi nickchase/rss-php-nginx:v1Untagged: nickchase/rss-php-nginx:v1Untagged: nickchase/rss-php-nginx@sha256:0a33c7a25a6d2db4b82517b039e9e21a77e5e2262206fdcac8b96f5afa64d96cDeleted: sha256:208c4fc237bb6b2d3ef8fa16a78e105d80d00d75fe0792e1dcc77aa0835455e3Deleted: sha256:d7de4d9c00136e2852c65e228944a3dea3712a4e7bcb477eb7393cd309be179b
- Se si esegue nuovamente docker images, si vedrà che è sparito:
# docker imagesREPOSITORY TAG IMAGE ID CREATO SIZEnginx ultimo abf312888d13 3 giorni fa 181.5 MBwebdevops/php-nginx ultimo 93037e4c8998 3 giorni fa 675.4 MBubuntu ultimo e4415b714b62 2 settimane fa 128.1 MBhello-world ultimo c54a2cc56cbb 5 mesi fa 1.848 kB
- Ora se si crea un nuovo contenitore basato su questa immagine, si vedrà che viene scaricato dal Docker Hub:
# docker run -dP nickchase/rss-php-nginx:v1
- Infine, testare il nuovo contenitore ottenendo la nuova porta…
# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES13a423324d80 nickchase/rss-php-nginx:v1 "/opt/docker/bin/entr" 6 secondi fa Up 5 secondi 0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp goofy_brahmagupta
- … e accedere al file rss.php.
curl http://localhost:32825/rss.php
Si dovrebbe vedere lo stesso output di prima.
Utilizzare un Dockerfile
Creare manualmente una nuova immagine da un contenitore esistente dà molto controllo, ma ha un lato negativo. Se il contenitore di base viene aggiornato, non avrete necessariamente i benefici di quei cambiamenti.
Per esempio, supponiamo che io voglia un contenitore che prenda sempre l’ultima versione di un sistema operativo Linux come il sistema operativo Ubuntu e costruisca su quello. Il metodo precedente non ci dà questo vantaggio.
Invece, possiamo usare un metodo chiamato Dockerfile, che ci permette di specificare una particolare versione di un’immagine base, o specificare che vogliamo usare sempre l’ultima versione.
Per esempio, diciamo che vogliamo creare una versione del contenitore rss-php-nginx che inizia con v1 ma serve sulla porta 88 (piuttosto che la tradizionale 80). Per farlo, vogliamo fondamentalmente eseguire tre passi:
- Iniziamo con il contenitore di base desiderato.
- Diremo a Nginx di ascoltare sulla porta 88 piuttosto che 80.
- Far sapere a Docker che il contenitore ascolta sulla porta 88.
Lo faremo creando un contesto locale, scaricando una copia locale del file di configurazione, aggiornandola, e creando un Dockerfile che include le istruzioni per costruire il nuovo contenitore.
Impostiamo il tutto.
- Create una directory di lavoro in cui costruire il nuovo contenitore. Come chiamarlo dipende completamente da voi. Io ho chiamato il mio K8stutorial.
- Dalla linea di comando, nel contesto locale, iniziate istanziando l’immagine in modo da avere qualcosa su cui lavorare:
# docker run -dP nickchase/rss-php-nginx:v1
- Ora fate una copia del file vhost.conf. In questo particolare contenitore, potete trovarlo in /opt/docker/etc/nginx/vhost.conf.
# docker cp amazing_minksy:/opt/docker/etc/nginx/vhost.conf .
Nota che ho un nuovo contenitore chiamato amazing_minsky per sostituire small_bassi. A questo punto dovreste avere una copia di vhost.conf nella vostra directory locale, quindi nel mio caso, sarebbe ~/k8stutorial/vhost.conf.
- Ora avete una copia locale del file vhost.conf. Utilizzando un editor di testo, aprire il file e specificare che nginx dovrebbe essere in ascolto sulla porta 88 piuttosto che sulla porta 80:
server { listen 88 default_server; listen 8000 default_server; server_name _ *.vm docker;...
- Successivamente, vogliamo andare avanti e creare il Dockerfile. Potete farlo in qualsiasi editor di testo. Il file, che dovrebbe essere chiamato Dockerfile, dovrebbe iniziare specificando l’immagine base:
FROM nickchase/rss-php-nginx:v1
- Qualsiasi contenitore che viene istanziato da questa immagine sarà in ascolto sulla porta 80, quindi vogliamo andare avanti e sovrascrivere quel file di configurazione Nginx con quello che abbiamo modificato:
FROM nickchase/rss-php-nginx:v1COPY vhost.conf /opt/docker/etc/nginx/vhost.conf
- Infine, dobbiamo dire a Docker che il contenitore ascolta sulla porta 88:
FROM nickchase/rss-php-nginx:v1COPY vhost.conf /opt/docker/etc/nginx/vhost.confEXPOSE 88
- Ora abbiamo bisogno di costruire l’immagine reale. Per farlo, useremo il comando docker build:
# docker build -t nickchase/rss-php-nginx:v2 .Sending build context to Docker daemon 2.048 kBStep 1 : FROM nickchase/rss-php-nginx:v1 ---> 208c4fc237bbStep 2 : EXPOSE 88 ---> In esecuzione in 23408def6214 ---> 93a43c3df834Rimozione del contenitore intermedio 23408def6214Realizzato con successo 93a43c3df834
Notare che abbiamo specificato il nome dell’immagine, insieme ad un nuovo tag (si può anche creare un’immagine completamente nuova) e la directory in cui trovare il Dockerfile ed eventuali file di supporto.
- Infine, spingiamo la nuova immagine nell’hub:
# docker push nickchase/rss-php-nginx:v2
- Testate la vostra nuova immagine istanziandola e richiamando la pagina di test. con un comando come docker run <image>. Per esempio:
# docker run -dP nickchase/rss-php-nginx:v2root@kubeclient:/home/ubuntu/tutorial# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES04f4b384e8e2 nickchase/rss-php-nginx:v2 "/opt/docker/bin/entr" 8 secondi fa Up 7 secondi 0.0.0.0:32829->80/tcp, 0.0.0.0:32828->88/tcp, 0.0.0.0:32827->443/tcp, 0.0.0.0:32826->9000/tcp goofy_brahmagupta13a423324d80 nickchase/rss-php-nginx:v1 "/opt/docker/bin/entr" 12 minuti fa Up 12 minuti 0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp amazing_minsky
Nota che ora hai una porta mappata per la porta 88 che puoi chiamare:
curl http://localhost:32828/rss.php
Altre cose che puoi fare con Dockerfile
Docker definisce un intero elenco di cose che puoi fare con un Dockerfile, come:
- .dockerignore
- FROM
- MAINTAINER
- RUN
- CMD
- EXPOSE
- ENV
- COPY
- ENTRYPOINT
- VOLUME
- USER
- WORKDIR
- ARG
- ONBUILD
- STOPSIGNAL
- LABEL
Come potete vedere, c’è un bel po’ di flessibilità qui. Puoi vedere la documentazione per maggiori informazioni, e wsargent ha pubblicato un buon cheat sheet di Dockerfile.
Andare avanti
Creare nuove immagini Docker che possono essere usate da te o da altri sviluppatori è abbastanza semplice. Avete la possibilità di creare manualmente e commettere le modifiche, o di scriptarle usando un Dockerfile.
Nel nostro prossimo tutorial, vedremo come usare YAML per gestire questi contenitori con Kubernetes.