Con i deployment abbiamo imparato a gestire l'esecuzione di un'applicazione compreso l'importantissimo ciclo di vita delle sue versioni. Quello che ci serve ora introdurre è la possibilità di esporre tale workload come servizio all'interno della nostra architettura: ciò di cui abbiamo bisogno è una componente Kubernetes detta Service.
Con un Service potremo pubblicare un deployment come servizio in rete e, a seconda dell'architettura in cui lo inseriremo, potremo offrirlo come servizio interno della nostra applicazione (ad esempio, un backend a disposizione di un frontend) oppure offrirlo come interfaccia diretta verso l'utenza.
Con un po' di fantasia possiamo immaginare quanto il service possa essere importante. Al giorno d'oggi, si parla di architetture a microservizi in cui il software non è più costituito da un unico blocco monolitico bensì da una rete di servizi che collaborano tra loro e che nel loro complesso costituiscono l'applicazione finale. In quest'ottica, possiamo immaginare ognuno di questi servizi come un deployment che espone le proprie funzionalità mediante Service con tutta l'affidabilità che Kubernetes garantisce.
Service con comandi imperativi
Come primo esempio, vediamo l'attivazione di un deployment e la sua esposizione mediante Service non tramite configurazione dichiarativa ma con un approccio più immediato basato su comandi imperativi. Come nelle lezioni precedenti anche le sperimentazioni che qui proponiamo potranno essere svolte sull'ambiente minikube
.
In questo caso abbiamo avviato un deployment con un server Web nginx:
$ kubectl create deployment my-server-web --image=nginx
deployment.apps/my-server-web created
$ kubectl expose deployment my-server-web --type=NodePort --port=80
service/my-server-web exposed
Ci siamo limitati ad impostare il minimo indispensabile. Per il deployment, abbiamo indicato solo l'immagine da usare mentre per il Service - attivato mediante il comando expose
-
abbiamo identificato:
- il deployment da esporre ovvero
my-server-web
; - il tipo di Service,
NodePort
, che indica che la porta su cui verrà esposto il servizio sarà una delle porte del nodo nel cluster (approfondiremo nelle prossime lezioni le tipologie di Service esistenti); - la porta su cui l'applicazione è in ascolto (trattandosi di un server Web nel nostro caso sarà la 80).
Con alcune interrogazioni tramite kubectl
potremo avere certezza delle componenti avviate:
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
my-server-web 1/1 1 1 10m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-server-web-7845995b58-l5j2t 1/1 Running 0 11m
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 443/TCP 11m
my-server-web NodePort 10.99.122.115 80:30599/TCP 10m
Come possiamo vedere abbiamo un deployment che ha attivato a sua volta un Pod e due Service: uno è my-server-web
che abbiamo appena creato, l'altro di nome kubernetes
è un Service di default che offre l'interazione con le API della piattaforma Kubernetes.
Per provare il tutto nell'ambiente minikube possiamo richiedere informazioni in merito al Service appena creato:
$ minikube service my-server-web
|-----------|---------------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|---------------|-------------|---------------------------|
| default | my-server-web | 80 | http://192.168.49.2:30599 |
|-----------|---------------|-------------|---------------------------|
Viene mostrato l'indirizzo e la porta locale su cui il servizio è in esecuzione: http://192.168.49.2:30599
. Trattandosi di un server Web potremo svolgere rapidamente un'interrogazione con un browser o con client curl
per verificare il funzionamento del deployment.
Configurazione dichiarativa
Procedendo alla realizzazione di un esempio analogo mediante configurazione dichiarativa scriviamo quanto segue nel file myserverweb.yaml
(abbiamo volutamente usato per le componenti nomi simili ai precedenti ma senza trattini al fine di evitare conflitti):
apiVersion: apps/v1
kind: Deployment
metadata:
name: myserverweb
labels:
server: web
spec:
selector:
matchLabels:
server: web
template:
metadata:
labels:
server: web
spec:
containers:
- name: server-web
image: nginx
Abbiamo così impostato un deployment mentre la configurazione del Service sarà collocata nel file myserverweb-service.yaml
:
apiVersion: v1
kind: Service
metadata:
name: myserverweb
spec:
selector:
server: web
type: NodePort
ports:
- port: 80
Anche in queste configurazioni abbiamo indicato gli aspetti essenziali che sono principalmente quelli utilizzati nel paragrafo precedente. Notiamo in questo caso però l'impiego di label. Queste sono caratteristiche che in Kubernetes hanno un'estrema importanza. Permettono di collegare tra loro le componenti mantenendo al contempo un elevato disaccoppiamento. Nel deployment abbiamo il template che specifica che ogni Pod che da esso sarà creato verrà etichettato con la proprietà di nome server
e valore web
: le etichette hanno essenzialmente una struttura chiave/valore.
Quanto indicato sempre nel deployment sotto la voce spec - selector - matchLabels
indica che a tale componente si faranno corrispondere tutti i Pod dotati dell'etichetta server: web
. A questo punto diventa chiaro il perché anche nel Service sia presente la medesima etichetta, sarebbe un po' come dirgli "I Pod che forniranno le funzionalità che esponi sono caratterizzati dall'etichetta server: web
che è quella con cui il deployment che li ha attivati li marchia".
Una volta avviate entrambe le componenti potremo vedere che il Service è in esecuzione e si potrà procedere a prove analoghe a quelle svolte nel precedente paragrafo:
$ kubectl apply -f myserverweb.yaml
deployment.apps/myserverweb created
$ kubectl apply -f myserverweb-service.yaml
service/myserverweb created
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 443/TCP 21s
myserverweb NodePort 10.96.17.61 80:30822/TCP 5s
A questo punto saremmo già pronti per creare più workload esposti da Service e portarli a dialogare tra loro ma lo vedremo nel prosieguo della guida.