È un po’ che non scrivo su questo blog ormai legacy… sarebbe meglio chiamarlo gitopstribe?! 😀
Direi di riprendere con un post recap che faccia da diario durante l’installazione di una piattaforma di laboratorio basata su Kubernetes.
La base di partenza è sempre il mio solito server Kimsufi (Server KS-9 – Intel W3520 – 16GB DDR3 ECC 1333 MHz – 240GB SSD) con Proxmox Virtual Environment 7.1-11.
Tramite Cloud Init ho configurato velocemente 3 nodi con sotto Rocky Linux 8.
Una volta preparate le VMs ho clonato il mitico KubeSpray e preparato un inventory Ansible ad-hoc in questo modo:
cp -rfp inventory/sample inventory/kimsufi
cat inventory/kimsufi/inventory.ini
[all]
k8s1 ip=10.10.10.4 etcd_member_name=etcd1
k8s2 ip=10.10.10.5 etcd_member_name=etcd2
k8s3 ip=10.10.10.6 etcd_member_name=etcd3
[kube_control_plane]
k8s1
k8s2
k8s3
[etcd]
k8s1
k8s2
k8s3
[kube_node]
k8s1
k8s2
k8s3
[calico_rr]
[k8s_cluster:children]
kube_control_plane
kube_node
calico_rr
Nell’inventory ho degli indirizzi privati e siccome dalla mia workstation non vedo direttamente la 10.10.10.0/24 in Proxmox ho configurato così il mio .ssh/config… ovvero faccio jump attraverso il nodo fisico ns304365.ip-94-23-211.eu.
Host k8s1
HostName 10.10.10.4
User rocky
ProxyCommand ssh -W %h:%p -lroot kimsufi
Host k8s2
HostName 10.10.10.5
User rocky
ProxyCommand ssh -W %h:%p -lroot kimsufi
Host k8s3
HostName 10.10.10.6
User rocky
ProxyCommand ssh -W %h:%p -lroot kimsufi
Host kimsufi
HostName ns304365.ip-94-23-211.eu
User root
Port 2222
ServerAliveInterval 20
Ecco come lanciare KubeSpray per l’installazione:
ansible-playbook -i inventory/kimsufi/inventory.ini --become --become-user=root cluster.yml
Se il provisioning via Ansible è andato a buon fine l’output è più o meno questo:
PLAY RECAP **************************************************************************************************************************************************************************************************************************
k8s1 : ok=766 changed=96 unreachable=0 failed=0 skipped=1319 rescued=0 ignored=9
k8s2 : ok=670 changed=84 unreachable=0 failed=0 skipped=1147 rescued=0 ignored=4
k8s3 : ok=684 changed=140 unreachable=0 failed=0 skipped=1149 rescued=0 ignored=4
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Una volta installato k8s ci si può connettere con kubectl.. ormai nulla di troppo sciamanico 😀
luckysideburn:kubespray$ ssh k8s1
Last login: Wed Jun 8 14:33:15 2022 from 10.10.10.1
[rocky@k8s1 ~]$ sudo su - root
Last login: Wed Jun 8 14:33:29 UTC 2022 on pts/0
[root@k8s1 ~]# kubectl get namespaces
NAME STATUS AGE
default Active 18m
kube-node-lease Active 18m
kube-public Active 18m
kube-system Active 18m
Siccome sono povero rendo i nodi “master” anche “worker” in modo da poter agire sia da control-plane che da esecutori di pod applicativi:
kubectl label node k8s1 node-role.kubernetes.io/worker=worker
kubectl label node k8s2 node-role.kubernetes.io/worker=worker
kubectl label node k8s3 node-role.kubernetes.io/worker=worker
… mi rendo conto che la cpu è insufficiente per girare pod applicativi… non mi resta che spengere massivamente il cluster, aumentare le CPU e farlo ripartire.. Uso ansible…
ansible all -m shell -a "sudo shutdown -h now" -i inventory/kimsufi/inventory.ini
Helm
Prima di tutto mi serve Helm per installare in futuro un po’ di robette utili prese dal landscape del CNCF.
Come riportato qui procedo con i seguenti step:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
Ci sono molti modi per installare Helm. Uso il metodo “script” per pigrizia. Basta un copia e incolla ed è fatta. Certo, lanciare script remoti alla cieca su un server è pericoloso, ma ci fidiamo della community 😀
[root@k8s1 ~]# ./get_helm.sh
Downloading https://get.helm.sh/helm-v3.9.0-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
Ingress Controller
Mi serve un ingress controller… Vado su quello che conosco meglio, ovvero Nginx! Come riportato qui procedo con un installazione standard via Helm.
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
Vessel Operator
Uso Vessel, un progetto tutto Sourcesense.
È un tool open-source che sviluppiamo e usiamo per controllare se sui nostri cluster Kube è tutto ok a livello di:
- Best practice (tipo: ho settato Limits e Requests?).
- CVE delle immagini.
Ecco come installarlo (trovate la guida sul repo ufficiale di Vessel Operator)
yum install make -y
# Clono il repo del Vessel Operator
git clone https://github.com/sourcesense/vessel-operator
cd vessel-operator/
# Controllo che tutto sia running
kubectl -n vessel-operator-system get ingress,pods,services
pod/vessel-operator-controller-manager-55694999c-h7ptc 2/2 Running 0 98s
# Creo un ServiceAccount con diritti di admin e reperisco il suo token. Per comodità uso cluster-admin ma è possibile anche usare dei ruoli ad-hoc!
kubectl create serviceaccount k8sadmin -n kube-system
kubectl create clusterrolebinding k8sadmin --clusterrole=cluster-admin --serviceaccount=kube-system:k8sadmin
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | (grep k8sadmin || echo "$_") | awk '{print $1}') | grep token: | awk '{print $2}'
Come prima cosa voglio ispezionare il namespace più importante di tutti, ovvero kube-system e vedere come è messo. Creo quindi una custom resource dedicata:
apiVersion: cache.sourcesense/v1alpha1
kind: Vessel
metadata:
name: vessel-sample
spec:
vessel_nsname: 'kube-system'
vessel_k8s_url: 'https://10.10.10.4:6443'
vessel_k8s_token: 'token super secret'
vessel_ingress_enable: true
vessel_ingress_host: 'vessel.sourcesense.com'
vessel_ingress_class: 'nginx'
La salvo in vessel-sample.yaml e applico con kubectl:
kubectl apply -f vessel-sample.yaml
# Verifico corretta installazione di Vessel
[root@k8s1 vessel-operator]# kubectl get vessel
NAME AGE
vessel-sample 6s
Ora ispezioniamo kube-system!
# Prendo IP del service di Vessel. C'è anche l'ingress ma lo chiamo dall'interno per cui va bene anche il service.
[root@k8s1 vessel-operator]# kubectl get svc -A | grep vessel-service
kube-system vessel-service ClusterIP 10.233.40.132 <none> 80/TCP 10m
A caccia di CVE Critical!
Di seguito interrogo Vessel per capire quali immagini hanno CVE a livello Critical o High. Vessel utilizza Trivy per trovare vulnerabilità nelle immagini in essere su un cluster k8s.
[root@k8s1 vessel-operator]# curl -s http://10.233.40.132/query?issue=HIGH_CVE | jq
{
"size": 20,
"count": 43,
"page": 1,
"result": [
{
"id": 1,
"name": "calico-kube-controllers",
"namespace": "kube-system",
"kind": "deployment",
"issue": "HIGH_CVE",
"issue_metadata": "{'id': 'CVE-2018-1098', 'severity': 'HIGH', 'package': 'go.etcd.io/etcd', 'name': 'etcd: Cross-site request forgery via crafted local POST forms', 'description': \"A cross-site request forgery flaw was found in etcd 3.3.1 and earlier. An attacker can set up a website that tries to send a POST request to the etcd server and modify a key. Adding a key is done with PUT so it is theoretically safe (can't PUT from an HTML form or such) but POST allows creating in-order keys that an attacker can send.\", 'link': 'https://avd.aquasec.com/nvd/cve-2018-1098'}",
"tool": "vessel.tools.trivy",
"current": true,
"created_at": "2022-06-08 15:26"
},
...
....
{
"id": 11,
"name": "nodelocaldns",
"namespace": "kube-system",
"kind": "daemonset",
"issue": "CRITICAL_CVE",
"issue_metadata": "{'id': 'CVE-2022-23219', 'severity': 'CRITICAL', 'package': 'libc-bin', 'name': 'glibc: Stack-based buffer overflow in sunrpc clnt_create via a long pathname', 'description': 'The deprecated compatibility function clnt_create in the sunrpc module of the GNU C Library (aka glibc) through 2.34 copies its hostname argument on the stack without validating its length, which may result in a buffer overflow, potentially resulting in a denial of service or (if an application is not built with a stack protector enabled) arbitrary code execution.', 'link': 'https://avd.aquasec.com/nvd/cve-2022-23219'}",
"tool": "vessel.tools.trivy",
"current": true,
"created_at": "2022-06-08 15:26"
},
....
......
kube-system rispetta le best practice più comuni? 😀
Ovviamente questa è una provocazione, ma vediamo cosa ha tirato fuori vessel!
curl -s http://10.233.40.132/query?kind=deployment | jq
{
"size": 20,
"count": 6,
"page": 1,
"result": [
{
"id": 48,
"name": "dns-autoscaler",
"namespace": "kube-system",
"kind": "deployment",
"issue": "missing_liveness_probe",
"issue_metadata": null,
"tool": "vessel.tools.linter",
"current": true,
"created_at": "2022-06-08 15:26"
},
{
"id": 49,
"name": "dns-autoscaler",
"namespace": "kube-system",
"kind": "deployment",
"issue": "missing_limits",
"issue_metadata": null,
"tool": "vessel.tools.linter",
"current": true,
"created_at": "2022-06-08 15:26"
}
]
}
Non ho trovato troppi problemi a parte i Limits e la LivenessProbe mancanti sul deployment del dns-autoscaler.
Conclusioni
Finalmente sono riuscito a prendere un paio d’ore per poter scrivere di nuovo un post su questo blog. Belle le call per carità, ma anche meno!
ToDo
- Continuare il setup di un equipaggiamento completo su questo cluster k8s installato con KubeSpray..
- Continuare gli sviluppi di Vessel e Vessel Operator.
- Fare meno call