Kubernetes Basics

Podstawy Kubernetes

Dowiedz się, jak hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Oryginalnym autorem tej strony jest Jorge (przeczytaj jego oryginalny post tutaj)

Architektura i podstawy

Co robi Kubernetes?

  • Pozwala na uruchamianie kontenera/kontenerów w silniku kontenerów.

  • Harmonogram umożliwia efektywne wykonywanie kontenerów.

  • Utrzymuje kontenery w stanie działającym.

  • Umożliwia komunikację między kontenerami.

  • Umożliwia techniki wdrażania.

  • Zarządza woluminami informacji.

Architektura

  • Node: system operacyjny z podem lub podami.

  • Pod: Opakowanie wokół kontenera lub wielu kontenerów. Pod powinien zawierać tylko jedną aplikację (zwykle pod uruchamia tylko 1 kontener). Pod jest sposobem, w jaki Kubernetes abstrahuje technologię kontenerów.

  • Service: Każdy pod ma 1 wewnętrzny adres IP z wewnętrznego zakresu węzła. Jednak może być również udostępniony za pomocą usługi. Usługa ma również adres IP, a jej celem jest utrzymanie komunikacji między podami, więc jeśli jeden pod umrze, nowa replika (z innym wewnętrznym adresem IP) będzie dostępna pod tym samym adresem IP usługi. Może być skonfigurowana jako wewnętrzna lub zewnętrzna. Usługa działa również jako wyważarka obciążenia, gdy 2 pody są podłączone do tej samej usługi. Po utworzeniu usługi można znaleźć punkty końcowe każdej usługi, uruchamiając kubectl get endpoints

  • Kubelet: Główny agent węzła. Komponent, który nawiązuje komunikację między węzłem a kubectl i może uruchamiać tylko pod (za pośrednictwem serwera API). Kubelet nie zarządza kontenerami, które nie zostały utworzone przez Kubernetes.

  • Kube-proxy: jest usługą odpowiedzialną za komunikację (usługi) między serwerem API a węzłem. Podstawą są IPtables dla węzłów. Bardziej doświadczeni użytkownicy mogą zainstalować inne kube-proxy od innych dostawców.

  • Kontener pomocniczy (Sidecar container): Kontenery pomocnicze to kontenery, które powinny działać razem z głównym kontenerem w podzie. Ten wzorzec kontenera pomocniczego rozszerza i ulepsza funkcjonalność istniejących kontenerów bez ich zmiany. Obecnie wiemy, że używamy technologii kontenerowej do opakowania wszystkich zależności potrzebnych do uruchomienia aplikacji w dowolnym miejscu. Kontener robi tylko jedną rzecz i robi to bardzo dobrze.

  • Proces główny:

  • Serwer API: Jest to sposób, w jaki użytkownicy i pod używają do komunikacji z procesem głównym. Tylko uwierzytelnione żądania powinny być dozwolone.

  • Scheduler: Harmonogramowanie oznacza dopasowanie podów do węzłów, aby Kubelet mógł je uruchomić. Ma wystarczającą inteligencję, aby zdecydować, który węzeł ma więcej dostępnych zasobów i przypisać do niego nowy pod. Należy zauważyć, że scheduler nie uruchamia nowych podów, tylko komunikuje się z procesem Kubelet działającym wewnątrz węzła, który uruchomi nowy pod.

  • Kube Controller Manager: Sprawdza zasoby, takie jak zbiory replik lub wdrożenia, aby sprawdzić, czy na przykład uruchomione jest prawidłowe liczba podów lub węzłów. Jeśli brakuje poda, skontaktuje się z planistą, aby uruchomić nowy. Kontroluje replikacje, tokeny i usługi konta w API.

  • etcd: Przechowywanie danych, trwałe, spójne i rozproszone. Jest to baza danych Kubernetes i magazyn klucz-wartość, w którym przechowuje pełny stan klastrów (każda zmiana jest tutaj rejestrowana). Komponenty takie jak Scheduler lub Kube Controller Manager zależą od tych danych, aby wiedzieć, jakie zmiany nastąpiły (dostępne zasoby węzłów, liczba uruchomionych podów...)

  • Cloud Controller Manager: Jest to specyficzny kontroler do sterowania przepływem i aplikacjami, np. jeśli masz klastry w AWS lub OpenStack.

Należy zauważyć, że może być wiele węzłów (uruchamiających wiele podów), może również być wiele procesów głównych, których dostęp do serwera API jest zrównoważony obciążeniowo, a ich etcd jest zsynchronizowane.

Woluminy:

Kiedy pod tworzy dane, które nie powinny zostać utracone, gdy pod zniknie, powinny być one przechowywane w woluminie fizycznym. Kubernetes umożliwia dołączenie woluminu do poda w celu zachowania danych. Wolumen może znajdować się na lokalnej maszynie lub w zdalnym magazynie. Jeśli uruchamiasz pod w różnych fizycznych węzłach, powinieneś użyć zdalnego magazynu, aby wszystkie pod mogły do niego uzyskać dostęp.

Inne konfiguracje:

  • ConfigMap: Możesz skonfigurować adresy URL do dostępu do usług. Pod pobierze dane stąd, aby wiedzieć, jak komunikować się z resztą usług (podów). Należy jednak pamiętać, że to nie jest zalecane miejsce do przechowywania poufnych danych!

  • Secret: To jest miejsce do przechowywania poufnych danych, takich jak hasła, klucze API... zakodowane w B64. Pod będzie mógł uzyskać dostęp do tych danych, aby używać wymaganych poświadczeń.

  • Deployments: Tutaj wskazuje się komponenty, które mają być uruchamiane przez Kubernetes. Użytkownik zwykle nie będzie bezpośrednio pracować z podami, podami są abstrahowane w ReplicaSets (liczba replikowanych tych samych podów), które są uruchamiane za pomocą wdrożeń. Należy zauważyć, że wdrożenia są przeznaczone dla aplikacji bezstanowych. Minimalną konfiguracją dla wdrożenia jest nazwa i obraz do uruchomienia.

  • StatefulSet: Ten komponent jest przeznaczony specjalnie dla aplikacji takich jak bazy danych, które muszą uzyskać dostęp do tego samego magazynu.

  • Ingress: Jest to konfiguracja, która służy do **udostępniania aplikacji

Infrastruktura PKI - Certyfikatowy Urząd CA:

  • CA jest zaufanym źródłem dla wszystkich certyfikatów w klastrze.

  • Umożliwia komponentom wzajemną weryfikację.

  • Wszystkie certyfikaty klastra są podpisane przez CA.

  • ETCd ma swój własny certyfikat.

  • typy:

  • certyfikat apiservera.

  • certyfikat kubeleta.

  • certyfikat schedulera.

Podstawowe działania

Minikube

Minikube można użyć do przeprowadzenia szybkich testów na kubernetes bez konieczności wdrażania całego środowiska kubernetes. Uruchomi on procesy master i node na jednym komputerze. Minikube będzie używać virtualboxa do uruchomienia node'a. Zobacz tutaj, jak go zainstalować.

$ minikube start
😄  minikube v1.19.0 on Ubuntu 20.04
✨  Automatically selected the virtualbox driver. Other choices: none, ssh
💿  Downloading VM boot image ...
> minikube-v1.19.0.iso.sha256: 65 B / 65 B [-------------] 100.00% ? p/s 0s
> minikube-v1.19.0.iso: 244.49 MiB / 244.49 MiB  100.00% 1.78 MiB p/s 2m17.
👍  Starting control plane node minikube in cluster minikube
💾  Downloading Kubernetes v1.20.2 preload ...
> preloaded-images-k8s-v10-v1...: 491.71 MiB / 491.71 MiB  100.00% 2.59 MiB
🔥  Creating virtualbox VM (CPUs=2, Memory=3900MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.20.2 on Docker 20.10.4 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaul

$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

---- ONCE YOU HAVE A K8 SERVICE RUNNING WITH AN EXTERNAL SERVICE -----
$ minikube service mongo-express-service
(This will open your browser to access the service exposed port)

$ minikube delete
🔥  Deleting "minikube" in virtualbox ...
💀  Removed all traces of the "minikube" cluster

Podstawy Kubectl

Kubectl to narzędzie wiersza poleceń dla klastrów Kubernetes. Komunikuje się z serwerem API procesu głównego, aby wykonywać działania w Kubernetes lub pytać o dane.

kubectl version #Get client and server version
kubectl get pod
kubectl get services
kubectl get deployment
kubectl get replicaset
kubectl get secret
kubectl get all
kubectl get ingress
kubectl get endpoints

#kubectl create deployment <deployment-name> --image=<docker image>
kubectl create deployment nginx-deployment --image=nginx
#Access the configuration of the deployment and modify it
#kubectl edit deployment <deployment-name>
kubectl edit deployment nginx-deployment
#Get the logs of the pod for debbugging (the output of the docker container running)
#kubectl logs <replicaset-id/pod-id>
kubectl logs nginx-deployment-84cd76b964
#kubectl describe pod <pod-id>
kubectl describe pod mongo-depl-5fd6b7d4b4-kkt9q
#kubectl exec -it <pod-id> -- bash
kubectl exec -it mongo-depl-5fd6b7d4b4-kkt9q -- bash
#kubectl describe service <service-name>
kubectl describe service mongodb-service
#kubectl delete deployment <deployment-name>
kubectl delete deployment mongo-depl
#Deploy from config file
kubectl apply -f deployment.yml

Panel Minikube

Panel umożliwia łatwe sprawdzenie, co jest uruchomione w minikube. Możesz znaleźć URL dostępu do niego w:

minikube dashboard --url


🔌  Enabling dashboard ...
▪ Using image kubernetesui/dashboard:v2.3.1
▪ Using image kubernetesui/metrics-scraper:v1.0.7
🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
http://127.0.0.1:50034/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

Przykłady plików konfiguracyjnych YAML

Każdy plik konfiguracyjny składa się z 3 części: metadata, specification (co ma zostać uruchomione) i status (pożądany stan). Wewnątrz specyfikacji pliku konfiguracyjnego dla wdrożenia można znaleźć szablon zdefiniowany za pomocą nowej struktury konfiguracji określającej obraz do uruchomienia:

Przykład wdrożenia + usługi zadeklarowane w tym samym pliku konfiguracyjnym (z tutaj)

Ponieważ usługa zazwyczaj jest powiązana z jednym wdrożeniem, można zadeklarować oba w tym samym pliku konfiguracyjnym (usługa zadeklarowana w tej konfiguracji jest dostępna tylko wewnętrznie):

apiVersion: apps/v1
kind: Deployment
metadata:
name: mongodb-deployment
labels:
app: mongodb
spec:
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-username
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: mongo-root-password
---
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
spec:
selector:
app: mongodb
ports:
- protocol: TCP
port: 27017
targetPort: 27017

Przykład konfiguracji zewnętrznej usługi

Ta usługa będzie dostępna z zewnątrz (sprawdź atrybuty nodePort i type: LoadBalancer):

---
apiVersion: v1
kind: Service
metadata:
name: mongo-express-service
spec:
selector:
app: mongo-express
type: LoadBalancer
ports:
- protocol: TCP
port: 8081
targetPort: 8081
nodePort: 30000

To jest przydatne do testowania, ale w produkcji powinieneś mieć tylko wewnętrzne usługi i Ingress do wystawienia aplikacji.

Przykład pliku konfiguracyjnego Ingress

To wystawi aplikację pod adresem http://dashboard.com.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dashboard-ingress
namespace: kubernetes-dashboard
spec:
rules:
- host: dashboard.com
http:
paths:
- backend:
serviceName: kubernetes-dashboard
servicePort: 80

Przykład pliku konfiguracyjnego dla tajemnic

Zauważ, jak hasła są zakodowane w B64 (co nie jest bezpieczne!)

apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
type: Opaque
data:
mongo-root-username: dXNlcm5hbWU=
mongo-root-password: cGFzc3dvcmQ=

Przykład ConfigMap

ConfigMap to konfiguracja, która jest dostarczana do podów, aby wiedziały, jak zlokalizować i uzyskać dostęp do innych usług. W tym przypadku każdy pod będzie wiedział, że nazwa mongodb-service to adres poda, z którym mogą się komunikować (ten pod będzie wykonywał mongodb):

apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-configmap
data:
database_url: mongodb-service

Następnie, wewnątrz konfiguracji deploymentu, ten adres można określić w następujący sposób, aby został załadowany do środowiska poda:

[...]
spec:
[...]
template:
[...]
spec:
containers:
- name: mongo-express
image: mongo-express
ports:
- containerPort: 8081
env:
- name: ME_CONFIG_MONGODB_SERVER
valueFrom:
configMapKeyRef:
name: mongodb-configmap
key: database_url
[...]

Przykład konfiguracji woluminu

Możesz znaleźć różne przykłady plików konfiguracyjnych yaml dla konfiguracji pamięci masowej na stronie https://gitlab.com/nanuchi/youtube-tutorial-series/-/tree/master/kubernetes-volumes. Zauważ, że woluminy nie są wewnątrz przestrzeni nazw

Przestrzenie nazw

Kubernetes obsługuje wiele wirtualnych klastrów opartych na tym samym fizycznym klastrze. Te wirtualne klastry nazywane są przestrzeniami nazw. Są one przeznaczone do użytku w środowiskach z wieloma użytkownikami rozproszonymi na wiele zespołów lub projektów. Dla klastrów z kilkoma do kilkudziesięciu użytkowników, nie powinieneś w ogóle musieć tworzyć lub myśleć o przestrzeniach nazw. Przestrzenie nazw powinny być używane tylko w celu lepszego kontrolowania i organizacji każdej części aplikacji wdrażanej w kubernetes.

Przestrzenie nazw zapewniają zakres dla nazw. Nazwy zasobów muszą być unikalne w obrębie przestrzeni nazw, ale nie między przestrzeniami nazw. Przestrzenie nazw nie mogą być zagnieżdżane wewnątrz siebie i każdy zasób Kubernetes może znajdować się tylko w jednej przestrzeni nazw.

Domyślnie istnieje 4 przestrzenie nazw, jeśli korzystasz z minikube:

kubectl get namespace
NAME              STATUS   AGE
default           Active   1d
kube-node-lease   Active   1d
kube-public       Active   1d
kube-system       Active   1d
  • kube-system: Nie jest przeznaczony do użytku przez użytkowników i nie powinno się go dotykać. Służy do procesów mastera i kubectl.

  • kube-public: Publicznie dostępne dane. Zawiera configmapę, która zawiera informacje o klastrze.

  • kube-node-lease: Określa dostępność węzła.

  • default: Przestrzeń nazw, którą użytkownik będzie używał do tworzenia zasobów.

#Create namespace
kubectl create namespace my-namespace

Należy zauważyć, że większość zasobów Kubernetes (np. podów, usług, kontrolerów replikacji i innych) znajduje się w pewnych przestrzeniach nazw. Jednak inne zasoby, takie jak zasoby przestrzeni nazw i zasoby niskiego poziomu, takie jak węzły i persistenVolumes, nie znajdują się w przestrzeni nazw. Aby zobaczyć, które zasoby Kubernetes są i które nie są w przestrzeni nazw:

kubectl api-resources --namespaced=true #In a namespace
kubectl api-resources --namespaced=false #Not in a namespace

Możesz zapisać przestrzeń nazw dla wszystkich kolejnych poleceń kubectl w tym kontekście.

kubectl config set-context --current --namespace=<insert-namespace-name-here>

Helm

Helm jest menedżerem pakietów dla Kubernetes. Pozwala na pakietowanie plików YAML i ich dystrybucję w publicznych i prywatnych repozytoriach. Te pakiety nazywane są Helm Charts.

helm search <keyword>

Helm to również silnik szablonów, który umożliwia generowanie plików konfiguracyjnych z zmiennymi:

Sekrety Kubernetes

Secret to obiekt, który zawiera poufne dane, takie jak hasło, token lub klucz. Takie informacje mogą być umieszczone w specyfikacji Podu lub w obrazie. Użytkownicy mogą tworzyć sekrety, a system również tworzy sekrety. Nazwa obiektu Secret musi być prawidłową nazwą poddomeny DNS. Przeczytaj tutaj oficjalną dokumentację.

Sekrety mogą być takie jak:

  • Klucze API, SSH.

  • Tokeny OAuth.

  • Poświadczenia, hasła (tekst zwykły lub b64 + szyfrowanie).

  • Informacje lub komentarze.

  • Kod łączenia z bazą danych, ciągi... .

W Kubernetes istnieją różne typy sekretów

Wbudowany typUżycie

Opaque

dowolne dane zdefiniowane przez użytkownika (domyślnie)

kubernetes.io/service-account-token

token konta usługi

kubernetes.io/dockercfg

zserializowany plik ~/.dockercfg

kubernetes.io/dockerconfigjson

zserializowany plik ~/.docker/config.json

kubernetes.io/basic-auth

poświadczenia do uwierzytelniania podstawowego

kubernetes.io/ssh-auth

poświadczenia do uwierzytelniania SSH

kubernetes.io/tls

dane dla klienta lub serwera TLS

bootstrap.kubernetes.io/token

dane tokena rozruchowego

Typ Opaque jest domyślny, to typowy para klucz-wartość zdefiniowany przez użytkowników.

Jak działają sekrety:

Poniższy plik konfiguracyjny definiuje sekret o nazwie mysecret z dwoma parami klucz-wartość username: YWRtaW4= i password: MWYyZDFlMmU2N2Rm. Definiuje również pod o nazwie secretpod, który będzie miał username i password zdefiniowane w mysecret wystawione jako zmienne środowiskowe SECRET_USERNAME i SECRET_PASSWOR. Będzie również montować sekret username wewnątrz mysecret w ścieżce /etc/foo/my-group/my-username z uprawnieniami 0640.

secretpod.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
---
apiVersion: v1
kind: Pod
metadata:
name: secretpod
spec:
containers:
- name: secretpod
image: nginx
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
volumeMounts:
- name: foo
mountPath: "/etc/foo"
restartPolicy: Never
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
mode: 0640
kubectl apply -f <secretpod.yaml>
kubectl get pods #Wait until the pod secretpod is running
kubectl exec -it  secretpod -- bash
env | grep SECRET && cat /etc/foo/my-group/my-username && echo

Tajemnice w etcd

etcd to spójny i wysoko dostępny sklep klucz-wartość używany jako magazyn danych klastra Kubernetes. Pozwala nam uzyskać dostęp do przechowywanych w etcd tajemnic:

cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep etcd

Zobaczysz, że certyfikaty, klucze i adresy URL są przechowywane w systemie plików. Po ich uzyskaniu będziesz w stanie połączyć się z etcd.

#ETCDCTL_API=3 etcdctl --cert <path to client.crt> --key <path to client.ket> --cacert <path to CA.cert> endpoint=[<ip:port>] health

ETCDCTL_API=3 etcdctl --cert /etc/kubernetes/pki/apiserver-etcd-client.crt --key /etc/kubernetes/pki/apiserver-etcd-client.key --cacert /etc/kubernetes/pki/etcd/etcd/ca.cert endpoint=[127.0.0.1:1234] health

Gdy już uda ci się nawiązać komunikację, będziesz w stanie uzyskać tajemnice:

#ETCDCTL_API=3 etcdctl --cert <path to client.crt> --key <path to client.ket> --cacert <path to CA.cert> endpoint=[<ip:port>] get <path/to/secret>

ETCDCTL_API=3 etcdctl --cert /etc/kubernetes/pki/apiserver-etcd-client.crt --key /etc/kubernetes/pki/apiserver-etcd-client.key --cacert /etc/kubernetes/pki/etcd/etcd/ca.cert endpoint=[127.0.0.1:1234] get /registry/secrets/default/secret_02

Dodawanie szyfrowania do ETCD

Domyślnie wszystkie tajemnice są przechowywane w postaci czystego tekstu wewnątrz etcd, chyba że zastosujesz warstwę szyfrowania. Poniższy przykład oparty jest na https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

encryption.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: cjjPMcWpTPKhAdieVtd+KhG4NN+N6e3NmBPMXJvbfrY= #Any random key
- identity: {}

Następnie musisz ustawić flagę --encryption-provider-config na kube-apiserver, aby wskazać lokalizację utworzonego pliku konfiguracyjnego. Możesz zmodyfikować /etc/kubernetes/manifest/kube-apiserver.yaml i dodać następujące linie:

containers:
- command:
- kube-apiserver
- --encriyption-provider-config=/etc/kubernetes/etcd/<configFile.yaml>

Przewiń w dół do volumeMounts:

- mountPath: /etc/kubernetes/etcd
name: etcd
readOnly: true

Przewiń w dół do volumeMounts do hostPath:

- hostPath:
path: /etc/kubernetes/etcd
type: DirectoryOrCreate
name: etcd

Weryfikacja, czy dane są zaszyfrowane

Dane są szyfrowane podczas zapisu do etcd. Po ponownym uruchomieniu kube-apiserver, nowo utworzone lub zaktualizowane tajemnice powinny być zaszyfrowane podczas przechowywania. Aby to sprawdzić, można użyć programu wiersza poleceń etcdctl, aby pobrać zawartość tajemnicy.

  1. Utwórz nową tajemnicę o nazwie secret1 w przestrzeni nazw default:

kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
  1. Za pomocą polecenia etcdctl odczytaj tę tajemnicę z etcd:

ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C

gdzie [...] muszą być dodatkowe argumenty do połączenia z serwerem etcd. 3. Zweryfikuj, czy przechowywana tajemnica ma prefiks k8s:enc:aescbc:v1:, co wskazuje, że dostawca aescbc zaszyfrował dane wynikowe. 4. Zweryfikuj, czy tajemnica jest poprawnie odszyfrowana podczas pobierania za pomocą interfejsu API:

kubectl describe secret secret1 -n default

powinno pasować do mykey: bXlkYXRh, mydata jest zakodowane, sprawdź dekodowanie tajemnicy, aby całkowicie odkodować tajemnicę.

Ponieważ tajemnice są szyfrowane podczas zapisu, wykonanie aktualizacji tajemnicy spowoduje zaszyfrowanie tego zawartości:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Ostateczne wskazówki:

Referencje

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated