Ridimensionamento di Scala: come Dockerize usando Kubernetes
Pubblicato: 2022-03-11Kubernetes è il nuovo arrivato, promettendo di aiutare a distribuire le applicazioni nel cloud e ridimensionarle più rapidamente. Oggi, quando si sviluppa un'architettura di microservizi, è piuttosto standard scegliere Scala per la creazione di server API.
Se c'è un'applicazione Scala nei tuoi piani e vuoi ridimensionarla in un cloud, allora sei nel posto giusto. In questo articolo mostrerò passo dopo passo come prendere un'applicazione Scala generica e implementare Kubernetes con Docker per avviare più istanze dell'applicazione. Il risultato finale sarà una singola applicazione distribuita come più istanze e carico bilanciato da Kubernetes.
Tutto ciò verrà implementato semplicemente importando il kit sorgente Kubernetes nella tua applicazione Scala. Tieni presente che il kit nasconde molti dettagli complicati relativi all'installazione e alla configurazione, ma è abbastanza piccolo da essere leggibile e facile da capire se vuoi analizzare cosa fa. Per semplicità, implementeremo tutto sul tuo computer locale. Tuttavia, la stessa configurazione è adatta per una distribuzione cloud reale di Kubernetes.
Cos'è Kubernetes?
Prima di entrare nei dettagli cruenti dell'implementazione, discutiamo di cos'è Kubernetes e perché è importante.
Potresti aver già sentito parlare di Docker. In un certo senso, è una macchina virtuale leggera.
Per questi motivi, è già uno degli strumenti più utilizzati per la distribuzione di applicazioni nei cloud. Un'immagine Docker è abbastanza facile e veloce da costruire e duplicabile, molto più facile di una macchina virtuale tradizionale come VMWare, VirtualBox o XEN.
Kubernetes integra Docker, offrendo un ambiente completo per la gestione delle applicazioni dockerizzate. Utilizzando Kubernetes, puoi facilmente distribuire, configurare, orchestrare, gestire e monitorare centinaia o addirittura migliaia di applicazioni Docker.
Kubernetes è uno strumento open source sviluppato da Google ed è stato adottato da molti altri fornitori. Kubernetes è disponibile nativamente sulla piattaforma cloud di Google, ma altri fornitori lo hanno adottato anche per i loro servizi cloud OpenShift. Può essere trovato su Amazon AWS, Microsoft Azure, RedHat OpenShift e anche altre tecnologie cloud. Possiamo dire che è ben posizionato per diventare uno standard per la distribuzione di applicazioni cloud.
Prerequisiti
Ora che abbiamo trattato le nozioni di base, controlliamo se sono installati tutti i software prerequisiti. Prima di tutto, hai bisogno di Docker. Se utilizzi Windows o Mac, hai bisogno di Docker Toolbox. Se stai usando Linux, devi installare il particolare pacchetto fornito dalla tua distribuzione o semplicemente seguire le indicazioni ufficiali.
Codificheremo in Scala, che è un linguaggio JVM. Ovviamente sono necessari Java Development Kit e lo strumento scala SBT installati e disponibili nel percorso globale. Se sei già un programmatore Scala, è probabile che questi strumenti siano già installati.
Se utilizzi Windows o Mac, Docker creerà per impostazione predefinita una macchina virtuale denominata default
con solo 1 GB di memoria, che può essere troppo piccola per eseguire Kubernetes. Nella mia esperienza, ho avuto problemi con le impostazioni predefinite. Ti consiglio di aprire la GUI di VirtualBox, selezionare la tua macchina virtuale default
e modificare la memoria ad almeno 2048 MB.
L'applicazione per clusterizzare
Le istruzioni in questo tutorial possono essere applicate a qualsiasi applicazione o progetto Scala. Affinché questo articolo abbia un po' di "carne" su cui lavorare, ho scelto un esempio usato molto spesso per dimostrare un semplice microservizio REST in Scala, chiamato Akka HTTP. Ti consiglio di provare ad applicare il kit sorgente all'esempio suggerito prima di tentare di usarlo sulla tua applicazione. Ho testato il kit rispetto all'applicazione demo, ma non posso garantire che non ci saranno conflitti con il codice.
Quindi, per prima cosa, iniziamo clonando l'applicazione demo:
git clone https://github.com/theiterators/akka-http-microservice
Quindi, verifica se tutto funziona correttamente:
cd akka-http-microservice sbt run
Quindi, accedi a http://localhost:9000/ip/8.8.8.8
e dovresti vedere qualcosa di simile nella seguente immagine:
Aggiunta del kit sorgente
Ora possiamo aggiungere il kit sorgente con un po' di Git magic:
git remote add ScalaGoodies https://github.com/sciabarra/ScalaGoodies git fetch --all git merge ScalaGoodies/kubernetes
Con ciò, hai la demo che include il kit sorgente e sei pronto per provare. Oppure puoi anche copiare e incollare il codice da lì nella tua applicazione.
Dopo aver unito o copiato i file nei tuoi progetti, sei pronto per iniziare.
A partire da Kubernetes
Una volta scaricato il kit, dobbiamo scaricare il binario kubectl
necessario, eseguendo:
bin/install.sh
Questo programma di installazione è abbastanza intelligente (si spera) da scaricare il binario kubectl
corretto per OSX, Linux o Windows, a seconda del sistema. Nota, il programma di installazione ha funzionato sui sistemi che possiedo. Si prega di segnalare eventuali problemi, in modo che io possa riparare il kit.
Dopo aver installato il binario kubectl
, puoi avviare l'intero Kubernetes nel tuo Docker locale. Corri:
bin/start-local-kube.sh
La prima volta che viene eseguito, questo comando scaricherà le immagini dell'intero stack Kubernetes e un registro locale necessario per archiviare le immagini. Potrebbe volerci del tempo, quindi sii paziente. Si noti inoltre che necessita di accessi diretti a Internet. Se sei dietro un proxy, sarà un problema in quanto il kit non supporta i proxy. Per risolverlo, devi configurare strumenti come Docker, curl e così via per utilizzare il proxy. È abbastanza complicato che consiglio di ottenere un accesso temporaneo illimitato.
Supponendo che tu sia stato in grado di scaricare tutto correttamente, per verificare se Kubernetes funziona correttamente, puoi digitare il seguente comando:
bin/kubectl get nodes
La risposta attesa è:
NAME STATUS AGE 127.0.0.1 Ready 2m
Nota che l'età può variare, ovviamente. Inoltre, poiché l'avvio di Kubernetes può richiedere del tempo, potrebbe essere necessario invocare il comando un paio di volte prima di vedere la risposta. Se non ricevi errori qui, congratulazioni, hai Kubernetes attivo e funzionante sul tuo computer locale.
Dockerizzazione dell'app Scala
Ora che Kubernetes è attivo e funzionante, puoi distribuire la tua applicazione al suo interno. In passato, prima di Docker, dovevi distribuire un intero server per eseguire la tua applicazione. Con Kubernetes, tutto ciò che devi fare per distribuire la tua applicazione è:
- Crea un'immagine Docker.
- Inseriscilo in un registro da cui può essere avviato.
- Avvia l'istanza con Kubernetes, che prenderà l'immagine dal registro.
Fortunatamente, è molto meno complicato che sembra, specialmente se stai usando lo strumento di compilazione SBT come fanno molti.
Nel kit ho incluso due file contenenti tutte le definizioni necessarie per creare un'immagine in grado di eseguire applicazioni Scala, o almeno quanto necessario per eseguire la demo HTTP di Akka. Non posso garantire che funzionerà con qualsiasi altra applicazione Scala, ma è un buon punto di partenza e dovrebbe funzionare per molte configurazioni diverse. I file da cercare per creare l'immagine Docker sono:
docker.sbt project/docker.sbt
Diamo un'occhiata a cosa contengono. Il file project/docker.sbt
contiene il comando per importare il plugin sbt-docker
:
addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.0")
Questo plugin gestisce per te la creazione dell'immagine Docker con SBT. La definizione Docker è nel file docker.sbt
e si presenta così:
imageNames in docker := Seq(ImageName("localhost:5000/akkahttp:latest")) dockerfile in docker := { val jarFile: File = sbt.Keys.`package`.in(Compile, packageBin).value val classpath = (managedClasspath in Compile).value val mainclass = mainClass.in(Compile, packageBin).value.getOrElse(sys.error("Expected exactly one main class")) val jarTarget = s"/app/${jarFile.getName}" val classpathString = classpath.files.map("/app/" + _.getName) .mkString(":") + ":" + jarTarget new Dockerfile { from("anapsix/alpine-java:8") add(classpath.files, "/app/") add(jarFile, jarTarget) entryPoint("java", "-cp", classpathString, mainclass) } }
Per comprendere appieno il significato di questo file, è necessario conoscere Docker abbastanza bene per comprendere questo file di definizione. Tuttavia, non entreremo nei dettagli del file di definizione Docker, perché non è necessario comprenderlo a fondo per costruire l'immagine.

l'SBT si occuperà di raccogliere tutti i file per te.
Nota che il percorso di classe viene generato automaticamente dal comando seguente:
val classpath = (managedClasspath in Compile).value
In generale, è piuttosto complicato raccogliere tutti i file JAR per eseguire un'applicazione. Utilizzando SBT, il file Docker verrà generato con add(classpath.files, "/app/")
. In questo modo, SBT raccoglie tutti i file JAR per te e costruisce un Dockerfile per eseguire la tua applicazione.
Gli altri comandi raccolgono i pezzi mancanti per creare un'immagine Docker. L'immagine verrà creata utilizzando un'immagine APT esistente per eseguire programmi Java ( anapsix/alpine-java:8
, disponibile su Internet nel Docker Hub). Altre istruzioni stanno aggiungendo gli altri file per eseguire l'applicazione. Infine, specificando un punto di ingresso, possiamo eseguirlo. Nota anche che il nome inizia apposta con localhost:5000
, perché localhost:5000
è dove ho installato il registro nello script start-kube-local.sh
.
Costruire l'immagine Docker con SBT
Per creare l'immagine Docker, puoi ignorare tutti i dettagli del Dockerfile. Devi solo digitare:
sbt dockerBuildAndPush
Il plug sbt-docker
creerà quindi un'immagine Docker per te, scaricando da Internet tutti i pezzi necessari, quindi eseguirà il push a un registro Docker avviato in precedenza, insieme all'applicazione Kubernetes in localhost
. Quindi, tutto ciò che serve è aspettare ancora un po' per avere la tua immagine cotta e pronta.
Nota, se si verificano problemi, la cosa migliore da fare è ripristinare tutto a uno stato noto eseguendo i seguenti comandi:
bin/stop-kube-local.sh bin/start-kube-local.sh
Questi comandi dovrebbero arrestare tutti i contenitori e riavviarli correttamente per preparare il registro a ricevere l'immagine creata e inviata da sbt
.
Avvio del servizio in Kubernetes
Ora che l'applicazione è impacchettata in un contenitore e inserita in un registro, siamo pronti per usarla. Kubernetes utilizza la riga di comando e i file di configurazione per gestire il cluster. Poiché le righe di comando possono diventare molto lunghe ed essere anche in grado di replicare i passaggi, sto usando i file di configurazione qui. Tutti i campioni nel kit sorgente sono nella cartella kube
.
Il nostro prossimo passo è lanciare una singola istanza dell'immagine. Un'immagine in esecuzione è chiamata, nel linguaggio Kubernetes, pod . Quindi creiamo un pod invocando il seguente comando:
bin/kubectl create -f kube/akkahttp-pod.yml
Ora puoi ispezionare la situazione con il comando:
bin/kubectl get pods
Tu dovresti vedere:
NAME READY STATUS RESTARTS AGE akkahttp 1/1 Running 0 33s k8s-etcd-127.0.0.1 1/1 Running 0 7d k8s-master-127.0.0.1 4/4 Running 0 7d k8s-proxy-127.0.0.1 1/1 Running 0 7d
Lo stato in realtà può essere diverso, ad esempio "ContainerCreating", possono essere necessari alcuni secondi prima che diventi "In esecuzione". Inoltre, puoi ottenere un altro stato come "Errore" se, ad esempio, dimentichi di creare l'immagine prima.
Puoi anche controllare se il tuo pod è in esecuzione con il comando:
bin/kubectl logs akkahttp
Dovresti vedere un output che termina con qualcosa del genere:
[DEBUG] [05/30/2016 12:19:53.133] [default-akka.actor.default-dispatcher-5] [akka://default/system/IO-TCP/selectors/$a/0] Successfully bound to /0:0:0:0:0:0:0:0:9000
Ora hai il servizio attivo e funzionante all'interno del container. Tuttavia, il servizio non è ancora raggiungibile. Questo comportamento fa parte della progettazione di Kubernetes. Il tuo pod è in esecuzione, ma devi esporlo in modo esplicito. In caso contrario, il servizio deve essere interno.
Creazione di un servizio
Creare un servizio e verificarne il risultato è una questione di esecuzione:
bin/kubectl create -f kube/akkahttp-service.yaml bin/kubectl get svc
Dovresti vedere qualcosa del genere:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE akkahttp-service 10.0.0.54 9000/TCP 44s kubernetes 10.0.0.1 <none> 443/TCP 3m
Si noti che la porta può essere diversa. Kubernetes ha allocato una porta per il servizio e l'ha avviato. Se stai usando Linux, puoi aprire direttamente il browser e digitare http://10.0.0.54:9000/ip/8.8.8.8
per vedere il risultato. Se stai utilizzando Windows o Mac con Docker Toolbox, l'IP è locale sulla macchina virtuale che esegue Docker e sfortunatamente è ancora irraggiungibile.
Voglio sottolineare qui che questo non è un problema di Kubernetes, ma è una limitazione di Docker Toolbox, che a sua volta dipende dai vincoli imposti da macchine virtuali come VirtualBox, che si comportano come un computer all'interno di un altro computer. Per superare questa limitazione, dobbiamo creare un tunnel. Per semplificare le cose, ho incluso un altro script che apre un tunnel su una porta arbitraria per raggiungere qualsiasi servizio che abbiamo implementato. Puoi digitare il seguente comando:
bin/forward-kube-local.sh akkahttp-service 9000
Nota che il tunnel non funzionerà in background, devi tenere la finestra del terminale aperta finché ne hai bisogno e chiuderla quando non hai più bisogno del tunnel. Mentre il tunnel è in esecuzione, puoi aprire: http://localhost:9000/ip/8.8.8.8
e infine vedere l'applicazione in esecuzione in Kubernetes.
Tocco finale: scala
Finora abbiamo "semplicemente" inserito la nostra applicazione in Kubernetes. Sebbene sia un risultato entusiasmante, non aggiunge troppo valore alla nostra distribuzione. Siamo risparmiati dallo sforzo di caricare e installare su un server e configurare un server proxy per esso.
Dove Kubernetes brilla è nel ridimensionamento. Puoi distribuire due, dieci o cento istanze della nostra applicazione modificando solo il numero di repliche nel file di configurazione. Facciamolo.
Arresteremo il singolo pod e avvieremo invece una distribuzione. Quindi eseguiamo i seguenti comandi:
bin/kubectl delete -f kube/akkahttp-pod.yml bin/kubectl create -f kube/akkahttp-deploy.yaml
Quindi, controlla lo stato. Anche in questo caso, puoi provare un paio di volte perché la distribuzione può richiedere del tempo per essere eseguita:
NAME READY STATUS RESTARTS AGE akkahttp-deployment-4229989632-mjp6u 1/1 Running 0 16s akkahttp-deployment-4229989632-s822x 1/1 Running 0 16s k8s-etcd-127.0.0.1 1/1 Running 0 6d k8s-master-127.0.0.1 4/4 Running 0 6d k8s-proxy-127.0.0.1 1/1 Running 0 6d
Ora abbiamo due pod, non uno. Questo perché nel file di configurazione che ho fornito, c'è il valore replica: 2
, con due nomi diversi generati dal sistema. Non entrerò nei dettagli dei file di configurazione, perché lo scopo dell'articolo è semplicemente un'introduzione per i programmatori Scala per avviare Kubernetes.
Ad ogni modo, ora ci sono due pod attivi. La cosa interessante è che il servizio è lo stesso di prima. Abbiamo configurato il servizio per bilanciare il carico tra tutti i pod etichettati akkahttp
. Ciò significa che non dobbiamo ridistribuire il servizio, ma possiamo sostituire la singola istanza con una replicata.
Possiamo verificarlo avviando nuovamente il proxy (se sei su Windows e lo hai chiuso):
bin/forward-kube-local.sh akkahttp-service 9000
Quindi, possiamo provare ad aprire due finestre di terminale e vedere i registri per ciascun pod. Ad esempio, nel primo tipo:
bin/kubectl logs -f akkahttp-deployment-4229989632-mjp6u
E nel secondo tipo:
bin/kubectl logs -f akkahttp-deployment-4229989632-s822x
Ovviamente, modifica la riga di comando di conseguenza con i valori che hai nel tuo sistema.
Ora prova ad accedere al servizio con due browser diversi. Dovresti aspettarti di vedere le richieste da dividere tra i più server disponibili, come nell'immagine seguente:
Conclusione
Oggi abbiamo appena graffiato la superficie. Kubernetes offre molte più possibilità, tra cui ridimensionamento e riavvio automatizzati, distribuzioni incrementali e volumi. Inoltre, l'applicazione che abbiamo utilizzato come esempio è molto semplice, senza stato con le varie istanze che non hanno bisogno di conoscersi. Nel mondo reale, le applicazioni distribuite devono conoscersi e modificare le configurazioni in base alla disponibilità di altri server. In effetti, Kubernetes offre un keystore distribuito ( etcd
) per consentire a diverse applicazioni di comunicare tra loro quando vengono distribuite nuove istanze. Tuttavia, questo esempio è volutamente sufficientemente piccolo e semplificato per aiutarti ad andare avanti, concentrandoti sulle funzionalità principali. Se segui il tutorial, dovresti essere in grado di ottenere un ambiente di lavoro per la tua applicazione Scala sulla tua macchina senza essere confuso da un gran numero di dettagli e perderti nella complessità.
- Sviluppo per il cloud nel cloud: sviluppo di BigData con Docker in AWS
- K8s/Kubernetes: AWS vs GCP vs Azure
- Un confronto della mesh del servizio Kubernetes