In tempi recenti si parla molto di Infrastructure-as-a-Service e di Platform-as-a-Service. Se nel primo caso il progetto Open Source più diffuso e interessante è certamente OpenStack, nel mondo PaaS non si può non menzionare OpenShift, sia nella sua versione OpenShift Enterprise 3 rilasciata da Red Hat tramite le modalità standard di subscription che la versione community Openshift Origin. Siamo ormai alla versione Enterprise 3.1 (corrispondente alla versione Origin 1.1) e a breve sarà disponibile la 3.2 con nuove features e molti bugfix.
OpenShift Enteprise 3 permette di implementare environment di build e deploy grazie all’uso di Linux Containers, molto utilizzati oggigiorno nell’erogazione di architetture a microservizi.
OpenShift non è un software monolitico ma un progetto composito che si avvale di diverse tecnologie, anch’esse Open Source, quali Docker, Kubernetes, Etcd, Open vSwitch. OpenShift aggiunge un layer ulteriore specifico per l’infrastruttura, ovvero build e deploy configuration, templates, e Source-To-Image per creare e la creazione delle build partendo dai repository Git. In particolare l’utility Source-to-Image è a mio avviso uno degli aspetti più interessanti e customizzabili di OpenShift in quanto permette di creare build image partendo direttamente dal codice sorgente su Git (mentre scrivo medito di fare un articolo specifico al riguardo).
Al netto dello “strato” OpenShift molti aspetti fondamentali quali la gestione dei nodi del cluster, la creazione di Pod e la loro schedulazione sui nodi, nonché il self-healing del nostro cluster viene svolto da Kubenetes.
Cosa è dunque Kubernetes? Nella home page del sito kubernetes.io troviamo la definizione seguente: “Kubernetes is an open-source system for automating deployment, operations and scaling of containerized applications.”
Nella pratica si tratta di uno strumento di orchestrazione che permette di deployare i container all’interno di un cluster di nodi (che chiameremo da ora in poi minions), gestire il networking dei servizi containerizzati e attuare dei metodi di self-healing del cluster. I container in Kubernetes sono deployati all’interndo di Pod. Un Pod è l’unità logica minima in Kubernetes che può contenere uno o più container che condividono risorse comuni quali network, storage, ecc. Spesso troviamo Pod composti di un solo container ma è possibile trovare più container che svolgono funzioni altamente correlate in un unico Pod.
Per quanto riguarda le configurazioni condivise Kubernetes fa uso di Etcd come key/value store per la gestione e la coordinazione del cluster. Etcd è un progetto open-source nato in seno a CoreOS che permette di scrivere/leggere configurazioni distribuite facendo uso di REST API.
La parte di networking è invece gestita dal servizio Flannel, anche questo progetto figlio della community CoreOS. Flannel è un servizio di network overlay che distribuisce subnet ai vari host del cluster e fa uso di Etcd come servizio per il mapping degli indirizzi e delle subnet.
Red Hat ha implementato la sua soluzione che combina Kubernetes e Docker in Red Hat Enterprise Linux Atomic Host 7 che consiste in una RHEL minimale concepita appositamente per Kubernetes e Docker e dove i Container sono il componente software centrale. La versione community, per chi avesse voglia di testarla è Project Atomic.
Dopo questa breve introduzione vediamo ora come installare un cluster Kubernetes su tre VM Centos 7.1+. Uno dei nostri nodi avrà il ruolo di master mentre altri due nodi saranno i minions del cluster.
Prerequisiti di installazione
Queste operazioni preliminari vanno svolte su tutti e tre i nodi.
Risoluzione nomi: è necessario configurare il DNS con per risolvere i nomi del nostro cluster. In questo lab useremo un classe C 192.168.122.0/24 e la seguente nomenclatura:
- 192.168.122.130 – kubemaster.traininglab.com
- 192.168.122.131 – kubeminion1.traininglab.com
- 192.168.122.132 – kubeminion2.traininglab.com
Se non si ha un DNS a disposizione, come nel caso di un lab creato con tre macchine virtuali, si può ricorrere all’editing dei singoli file /etc/hosts dei tre nodi.
Un esempio di file /etc/hosts compilato è il seguente:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.122.130 kubemaster kubemaster.traininglab.com
192.168.122.131 kubeminion1 kubeminion1.traininglab.com
192.168.122.132 kubeminion2 kubeminion2.traininglab.com |
Utilizziamo indirizzi statici e assicuriamoci che l’hostname dei vari nodi corrisponda a quanto definito nei file /etc/hosts o nei record DNS. Se avete problemi con la configurazione del networking in CentOS7/RHEL7 potete fare riferimento a questo articolo sul nostro blog.
Firewall: Stop e disable del servizio firewalld. Firewalld non è compatibile con i servizi di Kubernetes, in particolare con kube-proxy che sfrutta iptables per modificare di volta in volta le regole nei vari minions.
# systemctl disable firewalld && systemctl stop firewalld |
Time Sync: Verificare che tutti i nodi siano sincronizzati sulla stessa sorgente ntp e che il servizio ntpd o chronyd sia attivo. In RHEL7/CentOS7 il servizio ntp di default è Chrony che può svolgere sia il ruolo di client che di server ntp.
Per verificare lo stato del servizio chronyd ed eventualmente abilitarlo:
# systemctl status chronyd # systemctl start chronyd # systemctl enable chronyd |
Installazione Master
Installiamo i pacchetti necessari per il nostro master con il comando yum:
# yum install -y etcd kubernetes-master flannel |
Una volta installati i pacchetti iniziamo dalla configurazione di Etcd, il servizio che permette di propagare le configurazioni all’interno del nostro cluster. Configuriamo il file /etc/etcd/etcd.conf con i seguenti parametri in modo da metterlo in ascolto non più solo su localhost ma su indirizzi esterni:
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" ETCD_LISTEN_PEER_URLS="http://localhost:2380" ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379" |
Definiamo ora il Kubernetes Master. Per farlo andiamo a editare il file /etc/kubernetes/config con la seguente direttiva:
KUBE_MASTER="--master=http://kubemaster.traininglab.com:8080" |
Impostiamo il kube api server nel file /etc/kubernetes/apiserveer
KUBE_API_ADDRESS="--address=0.0.0.0" KUBE_ETCD_SERVERS="--etcd-servers=http://kubemaster.traininglab.com:2379" |
Fatto ciò possiamo avviare il servizio Etcd per passare poi a configurare Flannel:
# systemctl start etcd.service |
Andremo a creare una rete 10.20.0.0/16 e distribuiremo subnet con maschera a 24 bit utilizzando vxlan come backend. Per fare ciò prepariamo un piccolo file json come il seguente:
{ "Network": "10.20.0.0/16", "SubnetLen": 24, "Backend": { "Type": "vxlan", "VNI": 1 } } |
Dobbiamo caricare questa configurazione in Etcc: per farlo utilizziamo l’utility CLI etcdctl:
# etcdctl set atomic.io/network/config < flannel_cfg.json |
E infine dobbiamo far puntare Flannel al server Etcd che in questo caso è il nostro master stesso (una configurazione di livello enterprise prevederebbe degli Etcd server separati). Per fare questo editiamo il file /etc/sysconfig/flanneld e modifichiamo il seguente parametro:
FLANNEL_ETCD="http://kubemaster.traininglab.com:2379" |
Giunti a questo punto possiamo abilitare e avviare i servizi.
# systemctl restart etcd.service && systemctl enable etcd.service # systemctl start kube-apiserver.service && systemctl enable kube-apiserver.service # systemctl start kube-controller-manager.service && systemctl enable kube-controller-manager.service # systemctl start kube-scheduler.service && systemctl enable kube-scheduler.service # systemctl start flanneld.service && systemctl enable flanneld.service |
La configurazione del master è completata ma al momento non è stato ancora installato nessun minion. Vale la pena puntualizzare che i container vengono eseguiti nei minion e distribuiti in base ad un algoritmo definito dallo scheduler di Kubernetes.
Installazione Nodi
Passiamo ad installare dunque i minions. Faremo l’operazione sul nodo kubeminion1.traininglab.com lasciando al lettore l’onere di ripetere la configurazione sul secondo nodo.
Per prima cosa installiamo i pacchetti necessari, ovvero Docker, Flannel, e kubernetes-node
# yum install -y docker kubernetes-node flannel |
Una volta installati passiamo alla configurazione. Nel file /etc/kubernetes/config passiamo l’indirizzo/hostname del nostro master:
KUBE_MASTER="--master=http://kubemaster.traininglab.com:8080" |
Ogni nodo esegue il servizio kubelet che è un agent che legge dei PodSpec (yaml o json) dalkube api server e si assicura che i pod descritti vengano eseguiti sul nodo.
Impostiamo il servizio kubelet per mettersi in ascolto su 0.0.0.0 e quindi rispondere a qualsiasi interfaccia configurata con indirizzo IPv4. Inoltre, se non lo è già, commentiamo la linea KUBELET_HOSTNAME e definiamo l’indirizzo del nostro KUBELET_API_SERVER, che corrisponderà sempre al nostro master.
KUBELET_ADDRESS="--address=0.0.0.0" # KUBELET_HOSTNAME="--hostname-override=127.0.0.1" KUBELET_API_SERVER="--api-servers=http://kubemaster.traininglab.com:8080" |
E’ venuto il momento di configurare Flannel per utilizzare il servizio Etcd. Per fare ciò è necessario specificare l’Etcd server (che nel nostro caso corrisponde anche con il master) nel file /etc/sysconfig/flanneld.
Infine abilitiamo e avviamo i vari servizi.
# systemctl start docker && systemctl enable docker # systemctl start flanneld && systemctl enable flanneld # systemctl start kubelet && systemctl enable kubelet # systemctl start kube-proxy && systemctl enable kube-proxy |
L’operazione va ripetuta per il nodo kubeminion2.traininglab.com.
A questo punto possiamo verificare la configurazione generale dal Master e a tale scopo iniziamo ad utilizzare la CLI di Kubernetes. Il comando più comunemente usato è kubectl, utility CLI che permette di gestire in modo efficiente e rapido il nostro cluster. Chi conosce già OpenShift Enterprise/Origin non potrà fare a meno di notare la notevole somiglianza con il comando oc.
# kubectl get nodes |
Con questo comando otterremo un elenco dei nostri nodi simile a questo:
Prima di poter testare il deploy di un’applicazione dobbiamo occuparci di un’ultima faccenda rilevante. A fine installazione viene creato un ServiceAccount denominato default. Possiamo visualizzarlo con il comando
# kubectl get serviceaccount/default |
E ottenere informazioni dettagliate in formato yaml con i comandi
# kubectl get serviceaccount/default -o yaml |
Se preferiamo possiamo visualizzare il tutto in formato json:
# kubectl get serviceaccount/default -o json |
In questo service account non è ancora stato definito un secret e pertanto se provassimo a creare una nuova applicazione otterremmo il seguente errore:
Error from server: error when creating “mysql.yaml”: Pod “mysql” is forbidden:
no API token found for service account default/default, retry after the token is automatically created and added to the service account.
Per ovviare al problema è necessario generare una chiave privata con il comando openssl:
# openssl genrsa -out /path/to/my.key 2048 |
Successivamente aggiorniamo i servizi apiserver e controller-manager di Kubernetes per utilizzare questa chiave. Nel file /etc/kubernetes/apiserver modifichiamo i parametri opzionali:
KUBE_API_ARGS="--service_account_key_file=/path/to/my.key" |
E ripetiamo l’operazione nel file /etc/kubernetes/controller-manager
KUBE_CONTROLLER_MANAGER_ARGS="--service_account_private_key_file=/path/to/my.key" |
Fatto ciò riavviamo i servizi relativi:
# systemctl restart kube-apiserver.service # systemctl restart kube-controller-manager.service # systemctl restart etcd |
A questo punto dovremmo avere un API token definito per il serviceaccount default. Possiamo verifcare con il comando
# kubectl describe serviceaccounts/default |
Ci aspettiamo un output di questo tipo che mostra l’esistenza di un secret e di un token:
Test Finali
Possiamo passare alla fase finale di test, ovvero il deploy di un’applicazione. Per fare questo useremo un file yaml già preparato che definisce un Replication Controller. Di cosa si tratta esattamente? Si tratta di una risorsa che gestice il deploy dei pod e che si assicura che un determinato numero di repliche sia costantemente presente. Questo significa che se uno o più dei pod instanziati dal replication controller cade o viene eliminato questo creerà subito i nuovi necessari a mantenere costante il valore impostato di replica.
Prepariamo il seguente file nginx.yaml che conterrà le impostazioni del Replication Controller.
apiVersion: v1 kind: ReplicationController metadata: name: nginx spec: replicas: 3 selector: app: nginx template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 |
Una volta configurato il file possiamo caricarlo con il comando kubectl:
# kubectl create -f nging.yaml |
Attendiamo il tempo necessario ai nodi per scaricare dal docker registry le immagini nginx necessarie ad eseguire i nostri container. Per monitorare il processo di avvio possiamo eseguire il comando:
# kubectl get pods |
Alla fine dovremmo trovare una situazione simile alla seguente con 3 repliche attive del pod nginx. Notare lo standard della nomenclatura che segue il nome del replication controller con una sequenza random aggiuntiva.
Il Replication Controller definito precedentemente ha istanziato i tre pod che vengono eseguiti dai minion del nostro cluster. Andando a testare sui singoli nodi con il comando docker ps potremmo effettivamente vedere i container relativi in esecuzione.
Se non abbiamo il file yaml a disposizione un altro modo molto rapido per creare un Replication Controller e il relativo (o realitivi) pod è il seguente:
# kubectl run nginx --image=nginx --port=80 |
Con il seguente comando specifichiamo il nome dell’applicazione, l’immagine da usare (verrà ricercata con questo esatto nome sul registry di default) e la porta che esporrà il container.
Dopo aver creato i pod possiamo esporli all’esterno e renderli raggiungibili da tutti i nodi tramite la creazione di un service.
# kubectl expose rc nginx |
Testiamo il risultato con il seguente comando:
# kubectl describe svc nginx |
Abbiamo un Cluster IP 10.254.253.161 che è raggiungibile da qualunque nodo del cluster. Gli endpoint sono gli indirizzi interni dei pod creati precedentemente.
Da uno dei nodi è possibile raggiungere il servizio, proviamo ad esempio dal nodo kubemimion1.traininglab.com:
# curl http://10.254.253.161 |
Dovremmo aspettarci un output del genere:
Questa configurazione dei servizi rimane comunque molto semplicistica e basilare. Un passo successivo sarebbe quello di configurare il servizio appena testato in modo da essere raggiungibile anche dall’esterno mappando le porte dei nodi alle porte definite nel servizio tramite l’attributo NodePort da definire nella configurazione json/yaml del servizio. Da questo punto di vista OpenShift offre una buona soluzione, che è il Router, un container che viene eseguito nel cluster e che esegue il servizio HAProxy esponendo una url pubblica raggiungibiule dall’esterno. Il router in openshift può essere messo in HA utilizzando un servizio di IP Failover che a sua volta è un container che esegue il servizio Keepalived.
Lo scopo di questo articolo è di mostrare non tanto una configurazione ideale di Kubernetes ma di evidenziare in modo pratico quanto di Kubernetes sia presente in OpenShift anche dal punto di vista di gestione: tools che usano le medesime API, stesse risorse REST in formato json/yaml. Questo ci fa capire chiaramente che OpenShift può essere considerato sotto certi punti di vista una vera e propria “estensione” di Kubernetes.
Cosa aggiunge OpenShift? Moltissimo! Permette innanzitutto di gestire in modo efficiente il multi-tenancy, ovvero più progetti paralleli sullo stesso cluster completamente separati tra loro. Implementa dei meccanismi di build e deploy tipici di un environment DevOps tramite Source-to-Image e possibilità di implementare build e deploy automatici, (Continuous Integration e Continuous Delivery).
Conoscere bene Kubernetes quindi è fondamentale per saper comprendere il funzionamento di OpenShift Enteprise/Origin, configurarlo e amministrarlo correttamente anche in contesti mission-critical.
A tale scopo il consiglio è quello di valutare il corso RH270 (Managing Containers With Red Hat Enteprise Linux Atomic Host) e il corso DO280 (OpenShift Enterprise Administration) che coprono in modo esaustivo gli argomenti trattati in questo post.
Buon lavoro!