In questo tutorial
proveremo a creare un'immagine docker a partire da un microservizio
SpringBoot. L'immagine sarà poi eseguita in un Azure Container Service
orchestrato da Kubernetes che configureremo per fare autoscaling.
Applicando un carico di stress apprezzeremo come l'orchestratore manderà
in esecuzione altre istanze del microservizio per far fronte alle
richieste.
https://software.oreilly.com/ideas/microservices-for-java-developers/page/2/spring-boot-for-microservices?log-out
Possiamo seguire il tutorial fino a completare il paragrafo "Add the HTTP Endpoints" e saltare il resto. Sarà sufficiente avere un servizio esposto che risponda correttamente all'indirizzo /api/hola.
Aggiungiamo invece un nuovo entry point che ci servirà per stressare il servizio
Per stressare il servizio sarà sufficiente passare un input sufficientemente grande all'indirizzo /api/stress/x (ad esempio 9128811).
Per generare l'immagine sarà sufficiente utilizzare il comando
Sarà possibile verificare lo stato dell'assegnazione attraverso il comando
Prima di proseguire procediamo con alcune verifiche
Attraverso il comando kubectl get pods otteniamo la lista e lo stato dei pods sul cluster
Quindi abbiamo bisogno di monitorare lo stato dei pods.
Abbiamo almeno due opzioni: attraverso CLI con i comandi visti al passo precedente oppure attraverso un pannello di monitoring grafico che è sufficiente attivare con il seguente comando:
In questa fase abbiamo una sola istanza ed un carico minimo sulla CPU
In questo esempio eseguiremo ubuntu e utilizzeremo uno script bash. Abbiamo però bisogno di installare prima il comando wget, quindi ci dobbiamo preoccupare di creare un'immagine custom a partire dall'immagine ufficiale.
Creiamo un dockerfile allo scopo:
Una volta lanciato lo script sarà possibile vedere il container scalare. Il cluster impiega tempi nell'ordine di circa un minuto per replicare la nostra immagine:
Le istanze lanciate iniziano a ridurre il carico
Aumentiamo ulteriormente il carico
In questo caso è interessante notare che il container vorrebbe mandare in esecuzione tutte le 10 istanze del microservizio che avevamo definito
Terminiamo il carico e attendiamo qualche minuto per consentire lo scaling down
Il carico è tornato a 0 e quindi sono state terminate le istanze non più necessarie.
https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-windows-walkthrough
Requisiti
docker, account Azure, account dockerHub, azure CLI, kubectl, immagine dockerIl servizio SpringBoot
Prima di tutto abbiamo bisogno di un microservizio da eseguire e dockerizzare. Se non ne avete uno a disposizione è possibile crearlo in pochi semplici passi utilizzando SpringBoot. Si può fare riferimento a questa guida prima di procedere ai passi successivi.https://software.oreilly.com/ideas/microservices-for-java-developers/page/2/spring-boot-for-microservices?log-out
Possiamo seguire il tutorial fino a completare il paragrafo "Add the HTTP Endpoints" e saltare il resto. Sarà sufficiente avere un servizio esposto che risponda correttamente all'indirizzo /api/hola.
Aggiungiamo invece un nuovo entry point che ci servirà per stressare il servizio
@RequestMapping(method = RequestMethod.GET, value = "/stress/{var}", produces = "text/plain")
public String stress(@PathVariable("var") String var) throws UnknownHostException {
try {
fatt(Integer.parseInt(var));
} catch (NumberFormatException e) {
e.printStackTrace();
return "ERRORE: input numerico richiesto!";
}
return "delayed for " + var + " cycles";
}
La funzione fatt(int x) è una semplice implementazione del calcolo del fattoriale appesantita ulteriormente da una sequenza di calcoli trigonometrici.
private int fatt(int x) {
int i;
int f=1;
for(i=1; i<=x; i=i+1) {
f=f*i;
//incrementa il tempo di elaborazione
Math.tan(Math.atan( Math.tan(Math.atan( Math.tan(Math.atan( 28564 ))) )));
}
return f;
}
Lo scopo è ovviamente quello di generare un carico computazionale
perché sarà poi questa la metrica che utilizzeremo per configurare
l'autoscaling.Per stressare il servizio sarà sufficiente passare un input sufficientemente grande all'indirizzo /api/stress/x (ad esempio 9128811).
Creazione e distribuzione del microservizio su dockerhub
Creazione dell'immagine
Per poter creare l'immagine docker abbiamo bisogno di scrivere il dockerfile che andrà letto ed eseguito in fase di build. Supponendo di eseguire la compilazione attraverso un tool di automation mi dovrò solo preoccupare di indicare una eventuale immagine di partenza, le porte esposte e il comando di pacchettizzazione:
#FROM java:8 FROM frolvlad/alpine-oraclejdk8
#workdirWORKDIR /code
# Prepare by downloading dependencies
ADD target /code/target
EXPOSE 8080
CMD ["java", "-jar", "target/hola-springboot-1.0.jar"]
Altrimenti si può specificare puntualmente come comporre l'immagine e i comandi da eseguire per la compilazione:
#FROM java:8 FROM frolvlad/alpine-oraclejdk8
# Install mavenRUN wget http://ftp.fau.de/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
RUN tar -zxvf apache-maven-3.3.9-bin.tar.gz
RUN rm apache-maven-3.3.9-bin.tar.gz
RUN mv apache-maven-3.3.9 /usr/lib/mvn
ENV M2_HOME=/usr/lib/mvn
ENV M2=$M2_HOME/bin
ENV PATH $PATH:$M2_HOME:$M2RUN apk add --update bash && rm -rf /var/cache/apk/*
WORKDIR /code
# Prepare by downloading dependenciesADD pom.xml /code/pom.xml
RUN ["mvn", "dependency:resolve"]
# Adding source, compile and package into a fat jarADD src /code/src
RUN ["mvn", "package"]
ADD target /code/target
EXPOSE 8080
CMD ["java", "-jar", "target/hola-springboot-1.0.jar"]
Per una compilazione in locale ad esempio ho usato questo Dockerfile che aggiunge, rispetto al precedente, l'installazione di maven e
la compilazione. Il risultato finale voluto è una immagine contenente
una JVM e un jar autoconsistente output della compilazione.Per generare l'immagine sarà sufficiente utilizzare il comando
Verifichiamo l'immagine creatadocker build .
PS C:\Users\e.mattei> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
emattei/firsttag <none> f0b3301b99c4 7 weeks ago 304 MB
busybox latest c30178c5239f 7 weeks ago 1.11 MB
frolvlad/alpine-oraclejdk8 latest 8a75025e4288 2 months ago 170 MB
d4w/nsenter latest 9e4f13a0901e 10 months ago 83.8 kB
Notiamo che non ha alcun tag. Per poter caricare la nostra immagine su un registry abbiamo bisogno prima di assegnarle un tagdocker tag f0b3301b99c4 emattei/firsttag
Login su dockerhub
Accediamo a dockerhub per caricare l'immagine appena creatadocker login
Caricamento dell'immagine sul repository dockerhub
Con il comando push saremo in grado di caricare l'immagine sul registryA questo punto l'immagine sarà disponibile su dockerhub. Per qualsiasi altro registry docker i passi sono analoghi.docker push emattei/firsttag
3. Creazione del cluster kubernetes
A questo punto dobbiamo creare all'interno della sottoscrizione Azure tutte le risorse necessarie al deploy della nostra applicazione. Utilizzeremo il client azCreazione del resource-group
Creiamo il resource group che accoglierà le risorse
az group create --name=myKubernetesResourceGroup --location=westeurope
Creazione del cluster kubernetes
All'interno del resource group appena creato creiamo il cluster kubernetes. Dobbiamo scegliere un dns e contestualmente richiediamo la generazione della chiave ssh
az acs create --orchestrator-type=kubernetes \
--resource-group myKubernetesResourceGroup \
--name=myKubernetesClusterName \
--dns-prefix=myPrefix \
--agent-count=2 \
--generate-ssh-keys \
--windows --admin-username myWindowsAdminName \
--admin-password myWindowsAdminPassword
Scaricare la configurazione del cluster sul client kubectl
Per poter accedere al cluster appena creato attraverso il client kubectl dobbiamo scaricare la configurazione e le credenziali
az acs kubernetes get-credentials
--resource-group=myKubernetesResourceGroup --name=myKubernetesClusterName
A questo punto sarà possibile accedere al cluster kubernetes dalla macchina locale. Per verificarlo basta eseguire un comando tipo
kubectl get nodes
che restituisce la lista dei nodiCreazione del servizio autoscaled ed esposizione al pubblico
Creazione service e deployment
kubectl run kubeholahub --image=emattei/firsttag --requests=cpu=200m
--expose --port=8080
service "kubeholahub" created
deployment "kubeholahub" created
La porta esposta dal servizio kubernetes dovrà coincidere con la
porta esposta dall'immagine docker. In questo caso i microservizi
realizzati sono esposti sulla 8080.Definizione delle logice di autoscaling
E' possibile definire il numero di pods minimo, massimo e le soglie di scalabilità attraverso varie metriche. In questo caso richiediamo di scalare non appena il carico sulla cpu raggiunga il 30%.
kubectl autoscale deployment kubeholahub --cpu-percent=30 --min=1 --max=10
deployment "kubeholahub" autoscaled
Esposizione pubblica del servizio
Per verificare quanto realizzato e procedere con i test di carico vogliamo poter raggiungere i microservizi dall'esterno e pertanto creiamo un bilanciatore di carico
kubectl expose deployment kubeholahub --type=LoadBalancer
--name=kubeholahub-service
service "kubeholahub-service" exposed
Dopo alcuni di minuti Azure assegnerà al bilanciatore un indirizzo IP pubblico.Sarà possibile verificare lo stato dell'assegnazione attraverso il comando
kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubeholahub 1xx.xxx.xxx.xx1 <none> 8080/TCP 21m
kubeholahub-service 1xx.xxx.xxx.xx3 <pending> 8080:30885/TCP 20m
kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubeholahub 1xx.xxx.xxx.xx1 <none> 8080/TCP 1h
kubeholahub-service 1xx.xxx.xxx.xx3 1xx.xxx.xxx.x10 8080:30885/TCP 1h
Prima di proseguire procediamo con alcune verifiche
Attraverso il comando kubectl get pods otteniamo la lista e lo stato dei pods sul cluster
kubectl get pods
NAME READY STATUS RESTARTS AGE
kubeholahub-2216774994-4740k 1/1 Running 0 1m
Attraverso il comando kubectl get deployments otteniamo la lista dei deployments
kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kubeholahub 1 1 1 1 2m
Attraverso il comando kubectl get hpa possiamo vedere lo stato del horizontal autoscaling pod. Dovremmo ottenere qualcosa di simile a questo:
kubectl get hpa
NAME REFERENCE TARGET CURRENT MINPODS MAXPODS AGE
kubeholahub Deployment/kubeholahub 50% 3% 1 10 1h
Se tutto è in ordine siamo in grado di raggiungere dall'esterno il
microservizio esposto. Sarà sufficiente puntare all'IP assegnato da
azure chiamando le API di riferimento:http://1xx.xxx.xxx.xx0:8080/api/hola
Setup del monitoring e test di carico
Panello di monitoring
Ora abbiamo un microservizio in esecuzione su un cluster kubernetes con autoscaling definito. Il passo successivo sarà quello di eseguire dei test di carico per stressare la cpu oltre la soglia definita e vedere come reagisce l'orchestratore.Quindi abbiamo bisogno di monitorare lo stato dei pods.
Abbiamo almeno due opzioni: attraverso CLI con i comandi visti al passo precedente oppure attraverso un pannello di monitoring grafico che è sufficiente attivare con il seguente comando:
Il pannello sarà raggiungibile all'indirizzo http://localhost:8001/ui oppure http://localhost:8001/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/kubectl proxy
In questa fase abbiamo una sola istanza ed un carico minimo sulla CPU
Setup di un test di carico
Il test di carico consisterà semplicemente in una sequenza di request verso la API appositamente esposta. Per crearlo sfrutteremo ancora docker scaricando ed eseguendo una shell linux attraverso la quale potremo realizzare ed eseguire uno script nel nostro linguaggio preferito.In questo esempio eseguiremo ubuntu e utilizzeremo uno script bash. Abbiamo però bisogno di installare prima il comando wget, quindi ci dobbiamo preoccupare di creare un'immagine custom a partire dall'immagine ufficiale.
Creiamo un dockerfile allo scopo:
FROM ubuntu:latest
RUN apt-get update \
&& apt-get install -y wget \
&& rm -rf /var/lib/apt/lists/*
Creiamo l'immagine e la mandiamo in esecuzione:
docker build -t ubuntuwget .docker run -it --rm ubuntuwget
al prompt sarà sufficiente definire lo script nel modo seguente:Una volta lanciato lo script sarà possibile vedere il container scalare. Il cluster impiega tempi nell'ordine di circa un minuto per replicare la nostra immagine:
Le istanze lanciate iniziano a ridurre il carico
Aumentiamo ulteriormente il carico
In questo caso è interessante notare che il container vorrebbe mandare in esecuzione tutte le 10 istanze del microservizio che avevamo definito
kubectl autoscale deployment kubeholahub --cpu-percent=30 --min=1
--max=10deployment "kubeholahub" autoscaled
Ma le sette già create saturano la cpu assegnata.Terminiamo il carico e attendiamo qualche minuto per consentire lo scaling down
Il carico è tornato a 0 e quindi sono state terminate le istanze non più necessarie.
Riferimenti:
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthroughhttps://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-windows-walkthrough