În laboratoarele anterioare am folosit variabilele de mediu direct în fișierele YAML ale pod-urilor. Evident, în cazul datelor sensibile, nu este o idee bună pentru ca oricine are acces la fișierele de configurare YAML să aibă posibilitatea de a vedea variabilele de mediu definite. Problema apare în momentul în care în variabilele de mediu definim nume de utilizatori și parole.
Cum putem pasa datele sensibile în variabilele de mediu ale unui pod într-un mod sigur? Prin intermediul obiectelor Secrets din Kubernetes.
Exemplu de secret în Kubernetes
apiVersion: v1 data: POSTGRES_DB: bGlicmFyeQ== POSTGRES_PASSWORD: c3R1ZGVudA== POSTGRES_USER: c3R1ZGVudA== kind: Secret metadata: name: db-secret
Fișierul de mai sus este un secret care reprezintă variabilele de mediu folosite în podul DB. Puteți observa că perechile cheie-valoare nu sunt în totalitate clare. Mai exact, cheia este vizibilă, dar câmpul de valoare este encodat în format BASE64. Atenție, BASE64 nu este criptare, nu asigură siguranța.
Pentru a crea fișierul pentru un secret fără a converti manual string-urile în BASE64, puteți folosi comanda următoare:
kubectl create secret generic db-secret --from-literal=POSTGRES_USER=student --from-literal=POSTGRES_PASSWORD=student --from-literal=POSTGRES_DB=library --dry-run=client -o yaml > db-secret.
Creați acest secret:
kubectl apply -f db-secret.yaml
Incercati sa vizualizati datele din acest secret:
kubectl get secret db-secret kubectl describe secret db-secret
Cum injectăm aceste date în variabilele de mediu ale podului nostru DB? Să ne uităm pe următorul exemplu:
apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: db name: db spec: containers: - image: axonedge/lab-k8s-database name: db envFrom: - secretRef: name: db-secret
Creați pod-ul și verificați variabilele de mediu. Expuneti pod-ul de DB ca la laboratorul anterior, printr-un serviciu de tipul clusterIP.
Serviciul de RabbitMQ, fiind unul basic, îl rulăm în continuare ca la laboratorul anterior.
kubectl run rabbitmq --image=rabbitmq:3 --port=5672
și îl expunem printr-un serviciu de tipul ClusterIP:
apiVersion: v1 kind: Service metadata: name: rabbitmq-cluster-ip-service spec: ports: - port: 5672 protocol: TCP targetPort: 5672 selector: run: rabbitmq
Asa cum știm, pod-ul de procesator are mai multe variabile de mediu, le puteți vedea mai jos:
env: - name: PORT value: "8000" - name: PGHOST value: db-cluster-ip-service - name: PGDATABASE value: library - name: PGPORT value: "5432" - name: PGUSER value: student - name: PGPASSWORD value: student - name: AMQPURL value: amqp://rabbitmq-cluster-ip-service
Cum putem să injectăm aceste variabile fără să le avem în text clar în fișierul YAML?
Putem rezolva această problemă fie prin configMaps, fie prin secrets. Asa cum știm deja, perechile cheie-valoare dintr-un secret nu pot fi văzute din interiorul clusterului, deci secret rămâne cea mai bună opțiune pentru datele sensibile (PGDATABASE
, PGUSER
, PGPASSWORD
). În plus, putem folosi configMap pentru variabilele care nu sunt sensibile: PGHOST
, PGPORT
, AMQPURL
.
Așa cum am văzut anterior, creăm un secret cu datele mai sus menționate:
kubectl create secret generic proc-secret --from-literal=PGUSER=student --from-literal=PGPASSWORD=student --from-literal=PGDATABASE=library --dry-run=client -o yaml > proc-secret.yaml
Fișierul YAML este următorul:
apiVersion: v1 data: PGDATABASE: bGlicmFyeQ== PGPASSWORD: c3R1ZGVudA== PGUSER: c3R1ZGVudA== kind: Secret metadata: creationTimestamp: null name: proc-secret
Creați un configMap pentru restul variabilelor (urmăriți exemplul din documentație, este foarte similar cu cel de la secret):
apiVersion: v1 data: AMQPURL: amqp://rabbitmq-cluster-ip-service PGPORT: "5432" PGHOST: db-cluster-ip-service kind: ConfigMap metadata: name: proc-cmap
Injectați variabilele în pod:
apiVersion: v1 kind: Pod metadata: labels: run: procesator name: procesator spec: containers: - image: axonedge/lab-k8s-procesator name: procesator envFrom: - secretRef: name: proc-secret env: - name: AMQPURL valueFrom: configMapKeyRef: name: proc-cmap key: AMQPURL - name: PGPORT valueFrom: configMapKeyRef: name: proc-cmap key: PGPORT - name: PGHOST valueFrom: configMapKeyRef: name: proc-cmap key: PGHOST
Observați modul în care am injectat variabilele din configMap. Am exemplificat acest mod pentru scopul didactic, ele pot fi injectate la fel ca mai sus la secret.
Pentru pod-ul de API, urmăm pașii făcuți pentru procesator. Putem să observăm că datele sensibile sunt aceleași, deci putem să folosim același secret creat la pasul anterior. Restul variabilelor de mediu sunt și ele identice, dar avem în plus variabila PORT
. Pentru a exemplifica faptul că putem folosi envFrom
din mai multe surse, o sa creăm un nou configMap:
apiVersion: v1 data: AMQPURL: amqp://rabbitmq-cluster-ip-service PGPORT: "5432" PGHOST: db-cluster-ip-service PORT: "8000" kind: ConfigMap metadata: name: api-cmap
Acest configMap este identic cu cel de mai sus, dar a fost adăugată variabila PORT
.
Pentru a injecta variabilele de mediu în pod folosim envFrom
, ca in fișierul de mai jos:
apiVersion: v1 kind: Pod metadata: labels: run: api name: api spec: containers: - image: axonedge/lab-k8s-api name: api envFrom: - secretRef: name: proc-secret - configMapRef: name: api-cmap
Puteți observa ca am folosit două surse pentru envFrom
, o sursă fiind un secret, iar cealaltă sursă fiind un configMap. Astfel, am exeplificat cele mai folosite două metode prin care pot fi injectate variabilele dintr-un configMap într-un pod.
Modificați deployment-ul astfel încât să nu folosim pod-uri, ci să folosim deployment-uri acolo unde este nevoie.
Adăugați câteva intrări în baza de date. Verificați ca acestea există. Ștergeți podul de DB, creați unul nou și executați o cerere de tip GET. Mai avem intrări în baza de date?
Pentru ca datele din baza de date să nu dispară odată cu ștergerea pod-ului, folosim PV (PersistentVolume) și PVC (PersistentVolumeClaims).
Un fișier de bază pentru a crea un PersistentVolume găsiti mai jos. Proprietățile pe care le folosim aici sunt destul de intuitive.
apiVersion: v1 kind: PersistentVolume metadata: name: mypv spec: accessModes: - ReadWriteMany capacity: storage: 1Gi hostPath: path: /tmp/data
Dupa ce creăm acest volum, verificăm că acesta există:
kubectl get pv # în continuare urmăriți câmpul de status
Creăm un PVC pe baza următorului fișier de configurare:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mypvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi
După ce am creat și un PVC, verificați din nou atât PV, cât și PVC:
kubectl get pv # urmăriți câmpul de status
kubectl get pvc
Cum folosim un volum într-un pod? Modificăm fișierul de configurare al pod-ului pentru baza de date astfel încât acesta să folosească noul volum:
apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: db name: db spec: containers: - image: axonedge/lab-k8s-database name: db envFrom: - secretRef: name: db-secret volumeMounts: - mountPath: /var/lib/postgresql/data name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: mypvc
Ștergeți pod-ul de DB creat anterior și creați altul pe baza fișierului de mai sus. Adăugați câteva intrări în baza de date, ștergeți pod-ul și apoi creați altul nou. Putem observa că de data aceasta baza de date nu este goală.