È 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_rrNell’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 20Ecco come lanciare KubeSpray per l’installazione:
ansible-playbook -i inventory/kimsufi/inventory.ini --become --become-user=root cluster.ymlSe 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=0Una 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-namespaceVessel 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   6sOra 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
