Kubernetes stał się od pewnego czasu tym orkiestratorem skonteneryzowanych aplikacji, który podbija i zdobywa kolejne obszary rozwoju infrastruktury i architektury systemów informatycznych. Postanowiłem się zmierzyć z certyfikacją z tego obszaru. Egzaminy, z jakimi miałem do tej pory do czynienia, były inne, głównie sprowadzały się do testów jednokrotnego lub wielokrotnego wyboru. Tu jest inaczej, gdyż wszystkie zadania są w pełni praktyczne i wymagają przygotowania obiektu (zasobu), który następnie powinien być wdrożony na klaster. Czy warto zainteresować się taką certyfikacją? Myślę, że tak, ale należy ją potraktować jako przygodę, którą powinna usystematyzować wiedzę i ugruntować kompetencje w konfigurowaniu klastra za pomocą jedynie dokumentacji i natywnych narzędzi.
Aktualnie możliwe są trzy ścieżki
- CKA (Certified Kubernetes Administrator)
https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/
2. CKAD ( Certified Kubernetes Aplication Developer)
https://training.linuxfoundation.org/certification/certified-kubernetes-application-developer-ckad/
3. CKS ( Certified Kubernetes Security Specialist)
https://training.linuxfoundation.org/certification/certified-kubernetes-security-specialist/
Wybrałem ścieżkę administratora, chociaż jest ona najbardziej obszerna, to pozwala zweryfikować umiejętności radzenia sobie z klastrem jedynie za pomocą dokumentacji i narzędzia typu cli.
Egzamin przeprowadzany jest za pomocą wtyczki do Gogle Chrome
Całość pracy odbywa się w oknie przeglądarki. Pracujemy na systemie Ubuntu i mamy do dyspozycji drugą zakładkę , za pomocą której możemy korzystać z oficjalnej dokumentacji
Należy pamiętać, że zamiast Control+C (Copy) i Control+V mamy odpowiednio Control+Insert (Copy) i Shift+Insert (Paste).
Dostępny jest też notatnik, ale po jego uruchomieniu zasłania on ok 1/3 obszaru roboczego.
Mamy do zrobienia ok. 17 zadań w czasie dwóch godzin. Należy dobrze kontrolować czas. Każde z zadań ma podaną wagę, jeśli któreś z zadań sprawia nam trudności i utknęliśmy na dłużej niż 10 minut proponuję je oflagować (co umożliwia środowisko) i przejść do kolejnego.
Zakres egzaminu podzielony jest na pięć kategorii
- Cluster Architecture, Installation & Configuration, 25%
- Workloads & Scheduling, 15%
- Services & Networking, 20%
- Storage, 10%
- Troubleshooting, 30%
Ponieważ nie mamy za dużo czasu na przygotowanie rozwiązań, warto zastanowić się jak pracować w szybszym tempie.
Zwiększanie efektywności pracy
- Używanie aliasów
- Praca w trybie próbnym (–dry-run)
- Wdrażanie zmian na klastrze z przygotowanych manifestów YAML.
- Korzystanie z oficjalnej dokumentacji w postaci przygotowanych zakładek w przeglądarce)
1. Aliasy
alias k='kubectl'
alias kn='kubectl get nodes -o wide'
alias kp='kubectl get pods -o wide'
alias kd='kubectl get deployment -o wide'
alias ks='kubectl get svc -o wide'
Lista aliasów może być inna i o wiele większa, ważne, aby z nich korzystać regularnie podczas ćwiczeń.
2. Praca w trybie dry-run
Praca w trybie dry-run sprowadza się do skorzystania poniższego szablonu poleceń
kubectl <polecenie> --dry-run=client -o yaml > <plik.yaml>
Warto wyeksportować zmienną $do, która ułatwi nam pracę:
export do=" --dry-run=client -o yaml "
Od tego momentu, praca w wersji skróconej (wykorzystującej aliasy i zmienne środowiskowe) sprowadza się do postaci:
k <polecenie> $do > <plik.yaml>
W praktyce sprowadzi się do wykonywania poleceń typu:
# Create a pod nginx image
kubectl run nginx --image=nginx
# Create the pod template file with dry-run
kubectl run nginx --image=nginx --dry-run=client -o yaml>nginx.yaml
# Create a deployment
kubectl create deploy nginx-app --image=nginx --dry-run=client
# Scale a deployment
kubectl scale deploy nginx-app --replicas=3
# Create nginx-service and expose
kubectl expose pod nginx--name=nginx-service --port=80 --dry-run=client -o yaml>nginx.yaml
# Create a secret name my-secret with username admin
kubectl create secret generic my-secret --from-literal username=admin --dry-run=client -o yaml>nginx.yaml
3. Korzystanie z manifestów YAML
Przygotowujemy polecenie tworzenia obiektu w trybie imperatywnym, wyświetlamy jego postać w formacie YAML i zapisujemy w postaci pliku. Taki plik możemy modyfikować za pomocą edytora tekstu.
Unikałbym stosowania edycji obiektów bezpośrednio na klastrze
kubectl edit deployment nginx
Bardziej efektywne będzie zapisanie obiektu w postaci pliku, jego edycja, a następnie jego modyfikacji na klastrze za pomocą
kubectl apply -f plik.yaml
Sekwencja poleceń wygląda mniej więcej tak
kubectl get deployment nginx -o yaml > 01.deploy.nginx.yaml
vim 01.deploy.nginx.yaml
kubectl apply -f 01.deploy.nginx.yaml
Tu proponuję zastosować konwencję nazewnictwa pliku
numer_zadania:typ_obiektu:nazwa_obiektu.yaml
Można tez stosować inną konwencję, ale odpowiednio spójną wewnętrznie.
Dla tych, którzy chcą spróbować zabawy z klastrem przygotowałem piaskownicę w postaci środowiska w narzędziu Katacoda
Pozwala ono na godzinna zabawę.
Przykład utworzenia obiektu pod o nazwie test zbudowanego na obrazie nginx:1.16. Obiekt powinien zostać umieszczony w przestrzeni nazwa default
kubectl run test --image=nginx:1.16 -o yaml --dry-run=client
Na wyjściu pojawia się plik
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: test
name: test
spec:
containers:
- image: nginx
name: test
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
Zapiszmy go do pliku na nazwie 01.pod.test.yaml
kubectl run test --image=nginx:1.16 -o yaml --dry-run=client > 01.pod.test.yaml
vim 01.pod.nginx.yaml
Tutaj warto dodać brakujący namespace: default. To jest przydatne podczas tworzenia manifestów dla innych przestrzeni nazw i uwalnia nas to od dodawania do polecenia –namespace abc (lub -n abc).
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: test
name: test
namespace: default
spec:
containers:
- image: nginx:1.16
name: test
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
kubectl apply -f 01.pod.test.yaml
pod/test created
Spróbujmy po wdrożeniu zmienić nazwę kontenera, ale nie jego obraz
Zmieniamy w pliku jedną linię
name: test2
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: test
name: test
namespace: default
spec:
containers:
- image: nginx:1.16
name: test2
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
kubectl apply -f 01.pod.test.yaml
Próba wdrożenia tak zmodyfikowanego manifestu na klaster kończy się spektakularnie
The Pod "test" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations) core.PodSpec{ Volumes: []core.Volume{{Name: "default-token-dbflg", VolumeSource: core.VolumeSource{Secret: &core.SecretVolumeSource{SecretName: "default-token-dbflg", DefaultMode: &420}}}}, InitContainers: nil, Containers: []core.Container{ { - Name: "test2", + Name: "test", Image: "nginx:1.16", Command: nil, ... // 4 identical fields Env: nil, Resources: core.ResourceRequirements{}, - VolumeMounts: nil, + VolumeMounts: []core.VolumeMount{ + { + Name: "default-token-dbflg", + ReadOnly: true, + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }, VolumeDevices: nil, LivenessProbe: nil, ... // 10 identical fields }, }, EphemeralContainers: nil, RestartPolicy: "Always", ... // 24 identical fields }
Okazuje się, że nie wszystkie zmiany są możliwe w takim trybie. Można oczywiście usunąć obiekt z klastra i wdrożyć jeszcze raz. Czas na goni. Lepiej jest opanować tryb siłowy, która przyśpiesza pracę, ale nie używajcie go bezrefleksyjnie na produkcji (!).
W naszym przypadku nie można modyfikować nazwy kontenera, ale można modyfikować obraz, z którego korzysta.
kubectl apply -f 01.pod.test.yaml --force
pod/test configured
To samo może nam się przytrafić podczas edycji obiektu bezpośrednio na klastrze, a przecież pod to podstawowy obiekt wdrożeniowym. Nie należy o tym zapominać podczas pracy z obiektami, które kontrolują jego stan.
Dla bardziej skomplikowanych obiektów np. statefulset czy daemonset warto jest skorzystać z gotowych przykładów w dokumentacji i przystosować je do wymogów zadania.
4. Dokumentacja:
Dostępne strony są ograniczone do
Można mieć jednocześnie otwartą jedna zakładkę. Ponieważ dozwolone jest korzystanie z zakładek przeglądarki Chrome warto przygotować sobie dedykowaną listę zakładek.
Przykładowa lista zakładek https://gist.github.com/jonatasbaldin/4e76846ce8fb17e5d2fa64bdea594930
Oprócz dokumentacji można skorzystać z polecenia
kubectl explain
kubectl explain pod.spec.containers --recursive
5. YAML w Kubernetes
W tym miejscu warto poświęcić chwilę budowę manifestów YAML w Kubernetes.
Definicja obiektów Kubernetes w postaci manifestu YAML daje wiele zalet, m.in:
Wygoda: Nie ma potrzeby dodawania kolejnego parametru do linii poleceń
Utrzymanie: Pliki YAML można łatwo przechowywać w systemach kontroli wersji
Elastyczność: Można tworzyć o wiele bardziej skompilowane struktury niż z linii poleceń.
YAML jest nadzbiorem JSON, co oznacza, że każdy obiekt w formacie JSON jest poprawny w postaci YAML, ale w drugą stronę niekoniecznie.
Na szczęście YAML w Kubernetes jest prosty do nauki. Mamy tylko dwa typy struktur, które musimy opanować:
- Listy
- Mapy
Te dwie struktury mogą się przeplatać , czyli możemy mieć listę map i mapę list oraz każdą możliwą kombinację z obu stron.
Mapy
Zacznijmy od prostego, ale praktycznego przykładu
---
apiVersion: v1
kind: Pod
Pierwsza linia to separator obiektów, które można zapisać w jednym pliku. W praktyce nie polecam, wolę mieć poszczególne manifesty rozbite per plik.
Jest to o wiele bardziej elastyczne, jeśli zaczynami stosować systemy kontroli wersji. Wtedy mamy pełną kontrolę nad zmianami w każdym obiekcie niezależnie.
Mapa to para klucz: wartość. Nasz przykład zawiera dwie mapy, gdzie klucz od wartości oddziela znak :
klucz apiVersion o wartości v1
klucz kind o wartości Pod.
Odpowiednik w notacji JSON wygląda tak:
{
"apiVersion": "v1",
"kind": "Pod"
}
Większość manifestów w Kubernetes ma strukturę akms (apiVersion, kind, metadata, spec)
apiVersion: v1
kind:
metadata:
spec:
W porównaniu do JSON znaki “” są opcjonalne.
Można też zbudować struktury, gdzie klucz nie wskazuje na wartość, ale na kolejną parę klucz:wartość, czyli na mapę
---
apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
Klucz metadata wskazuje na parę name: rss-site. Para jest przeniesiona do następnej lini z odpowiednim wcięciem w stosunku do klucza nadrzędnego (metadata). Takie konstrukcje można swobodnie zagnieżdżać. Wcięcia realizowane są za pomocą spacji. Należy unikać i nie stosować w plikach YAML tabulacji. Z reguły stosuje się dwie spacje, ale może być ich więcej. Ważne, by było to spójne w obrębie pliku.
Odpowiednik w notacji JSON wygląda tak:
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "rss-site",
"labels": {
"app": "web"
}
}
}
Listy
Listy w YAML to sekwencja obiektów:
args:
- sleep
- "1000"
- message
- "Bring back Firefly!"
Każdy z elementów listy rozpoczyna się od znaku – wciętego względem rodzica i w praktyce pozwala to na budowę nieskończonie długiej struktury.
Odpowiednik w notacji JSON wygląda tak:
{
"args": ["sleep", "1000", "message", "Bring back Firefly!"]
}
Oczywiście poszczególne elementy listy mogą być mapami.
---
apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
- name: front-end
image: nginx
ports:
- containerPort: 80
- name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
- containerPort: 88
Mamy tutaj listę obiektów kontener (containers), z których każdy składa się z imienia (name), obrazu (image) i listy portów (czyli listę w liście).
Każdy element listy wcięty poniżej znacznika ports jest listą zawierającą mapę o nazwie containerPort i wartości 80 (lub 88).
Dla porównania, spójrzmy na odpowiednik w postaci JSON:
{ “apiVersion”: “v1”, “kind”: “Pod”, “metadata”: { “name”: “rss-site”, “labels”: { “app”: “web” } }, “spec”: { “containers”: [{ “name”: “front-end”, “image”: “nginx”, “ports”: [{ “containerPort”: “80” }] }, { “name”: “rss-reader”, “image”: “nickchase/rss-php-nginx:v1”, “ports”: [{ “containerPort”: “88” }] }] } }
Sami szybko dojdziemy do wniosku, że YAML czyta się (i modyfikuje) znacznie lepiej niż JSON. Dla API Kubernetes nie ma to żadnego znaczenia.
Podsumowując, mamy do dyspozycji:
- mapy, które są grupą struktur klucz: wartość
- listy,które są zbiorem indywidualnych wartości
- mapy złożone z map
- mapy złożone z list
- listy złożone z list
- listy złożone z map
Te dwie struktury pozwalają nam złożyć razem bardziej skomplikowane manifesty.
Zacznijmy od najprostszego obiektu jakim jest pod.
Zapiszmy taki plik w postaci pliku pod.yaml
—
apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
– name: front-end
image: nginx
ports:
– containerPort: 80
– name: rss-reader
image: nickchase/rss-php-nginx:v1
ports:
– containerPort: 88
Wdrożenie obiektu na klaster jest realizowane za pomocą opcji -f, która wskazuje plik zawierający przygotowany manifest.
kubectl create -f pod.yaml
W następnych częściach postaram się opisać najczęściej spotykane zagadnienia w poszczególnych obszarach tematycznych zakresu egzaminu.
6. Kursy
Pośród wielu kursów, które można znaleźć na różnych platformach szkoleniowych wybrałem
https://www.udemy.com/course/certified-kubernetes-administrator-with-practice-tests/
Było to dobrze wydane kilkadziesiąt złotych. Warto zrobić go spokojnie i po kolei, jedną z głównych zalet są przygotowane ćwiczenia. Te przygotowane laboratoria stanowią cenny dodatek, który pozwala nam na nabranie wprawy manualnej i ułatwia poruszanie się po nieco ascetycznym środowisku egzaminacyjnym.
Następne części
Certified Kubernetes Administrator (CKA) krok po kroku – część 2
Literatura:
https://www.kubernative.net/de/17-tutorial/69-exam-experience-cka
https://github.com/David-VTUK/CKA-StudyGuide
https://www.mirantis.com/blog/introduction-to-yaml-creating-a-kubernetes-deployment/
https://www.katacoda.com/djkormo/scenarios/kubernetes-sandbox
marcin
brawo, dobra robota, świetnie zebrane i opisane informacje 😉