Tworzenie klastra:
cat kind-cluster.ymlapiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- extraPortMappings:
- containerPort: 30000
hostPort: 8080(konfiguracja w liniach 4-6 będzie potrzeba później)
kind create cluster --config=kind-cluster.yamlSprawdzanie wersji klastra i klienta:
kubectl versionClient Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.5", GitCommit:"6b1d87acf3c8253c123756b9e61dac642678305f", GitTreeState:"archive", BuildDate:"2021-03-25T19:02:48Z", GoVersion:"go1.16.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-21T01:11:42Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}Wyświetlanie węzłów i informacji o nich:
kubectl get nodesNAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 21h v1.20.2Wyświetlanie informacji o węźle:
kubectl describe nodes kind-control-planePodstawowe informacje:
Name: kind-control-plane
Roles: control-plane,master
Labels: kubernetes.io/arch=amd64
kubernetes.io/os=linux
kubernetes.io/hostname=kind-control-planeStan węzła:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
MemoryPressure False Thu, 13 May 2021 14:17:48 +0200 Thu, 13 May 2021 14:16:44 +0200 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Thu, 13 May 2021 14:17:48 +0200 Thu, 13 May 2021 14:16:44 +0200 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Thu, 13 May 2021 14:17:48 +0200 Thu, 13 May 2021 14:16:44 +0200 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Thu, 13 May 2021 14:17:48 +0200 Thu, 13 May 2021 14:17:48 +0200 KubeletReady kubelet is posting ready statusInformacje systemowe:
Capacity:
cpu: 4
ephemeral-storage: 943866732Ki
hugepages-2Mi: 0
memory: 16342612Ki
pods: 110
System Info:
Machine ID: a60fe24d491d4e21abffdff148eeb94f
System UUID: 047d06bd-5543-44b7-8931-1e43bfdf3e96
Boot ID: d7f77d28-e0a0-4539-bcde-0d0a1d1e3d02
Kernel Version: 5.11.15-zen1-2-zen
OS Image: Ubuntu 20.10
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://1.4.0-106-gce4439a8
Kubelet Version: v1.20.2
Kube-Proxy Version: v1.20.2Każdy obiekt w Kubernetes istnieje jako zasób RESTful w unikalnej ścieżce HTTP
Polecenia kubectl wywołują odpowiednie zapytania
(przygotowując nagłówki, itp.) oraz przedstawiają wynik domyślnie w
sposób czytelny dla człowieka
Korzystając z przełącznika -o json lub
-o yaml można otrzymać wynik w innym formacie:
kubectl get nodes -o yamlAby wyodrębnić interesujące informacje, można wykorzystać składnie JSONPath:
kubectl get nodes -o jsonpath --template='{.items[0].status.addresses}' | jq .[
{
"address": "172.20.0.2",
"type": "InternalIP"
},
{
"address": "kind-control-plane",
"type": "Hostname"
}
]Obiekty Kubernetes są reprezentowane jako pliki JSON lub YAML
Te same pliki są zwracane w odpowiedzi jak i przesyłane w ramach żądania
Aby przesłać opis obiektu do systemu Kubernetes:
kubectl apply -f obj.yamlPonieważ obiekty w Kubernetes opisane są w sposób deklaratywny,
to komenda kubectl apply za pierwszym razem stworzy nowy
obiekt, a przy kolejnych wywołaniu zakończy bez wykonywania
czegokolwiek
Podobnie jak deklaratywne reguły w Ansible, komenda
kubectl apply może wprowadzić zmiany w istniejącym obiekcie
by był zgodny z opisem w pliku JSON/YAML, jednak możliwości wprowadzania
zmian są ograniczone do niewielkich korekt (patrz: niemutowalność ->
zastępowanie kontenerów nowymi w przypadku zmian)
Aby usunąć obiekt:
kubectl delete -f obj.yamlAlbo:
kubectl delete <type> <name>Kapsuła to zbiór kontenerów i woluminów działających w tym samym środowisku (na tej samej maszynie)
W Kubernetes, kapsuła (a nie kontener) to najmniejsza jednostka, którą można wdrożyć
W obrębie kapsuły, wszystkie kontenery współdzielą adres IP, nazwę hosta i przestrzeń portów, a także mogą komunikować się przy pomocy kanałów komunikacji międzyprocesowej (IPC)
Aplikacje z różnych kapsuł są w pełni odizolowane, niezależnie od tego czy działają na tej samej maszynie, czy na różnych
Projektując aplikację, powinniśmy w kapsule umieszczać razem tylko te kontenery, które absolutnie muszą być razem, bo inaczej aplikacje nie będzie działać poprawnie
Warto zauważyć, że połączenie sieciowe (np. backend <-> baza danych) możliwe jest pomiędzy kapsułami, więc powinniśmy rozdzielić komunikujące się sieciowo kontenery na osobne kapsuły
Przykładowy opis kapsuły nginx-pod.yml:
Utworzenie kapsuły:
kubectl apply -f nginx-pod.ymlWypisanie kapsuł:
kubectl get podsNAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 2mWypisanie szczegółowych informacji o kapsule:
kubectl describe pods nginx-podName: nginx-pod
Namespace: default
Priority: 0
Node: kind-control-plane/172.20.0.2
Start Time: Thu, 13 May 2021 16:08:05 +0200
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.0.7
IPs:
IP: 10.244.0.7
Containers:
nginx-container:
Container ID: containerd://3805d70f6bab0d272e217b4f330bf23e14da0045b9434aee836f31d5d601d77c
Image: nginx
Image ID: docker.io/library/nginx@sha256:df13abe416e37eb3db4722840dd479b00ba193ac6606e7902331dcea50f4f1f2
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Thu, 13 May 2021 16:08:09 +0200
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-t7mlq (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-t7mlq:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-t7mlq
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m22s default-scheduler Successfully assigned default/nginx-pod to kind-control-plane
Normal Pulling 3m21s kubelet Pulling image "nginx"
Normal Pulled 3m19s kubelet Successfully pulled image "nginx" in 1.903555704s
Normal Created 3m18s kubelet Created container nginx-container
Normal Started 3m18s kubelet Started container nginx-containerAby stworzyć tunel z lokalnego komputera, przez węzeł Kubernetes, aż do kapsuły:
kubectl port-forward nginx-pod 8080:80Aby sprawdzić logi (standardowy strumień wyjścia i błędów):
kubectl logs nginx-podAby uruchomić komendę (np. interaktywny shell):
kubectl exec -it nginx-pod -- bashAby skopiować pliki (w obie strony):
kubectl cp index.html nginx-pod:/usr/share/nginx/html/spec.containers[].image z publicznego rejestru
DockerowegoWczytać obraz do klastra:
kind load docker-image <tag-name>Skonfigurować dla kapsuły jej politykę ściągania obrazów (pole
spec.containers[].imagePullPolicy) na
wartość IfNotPresent albo Never
httpGet, żeby wysłać żądanie GET i
sprawdzić czy przyjdzie odpowiedźtcpSocket, żeby spróbować połączyć się na zadany port
TCPexec, żeby wykonać komendę i sprawdzić jej kod
wyjściaapiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- image: nginx
name: nginx-container
ports:
- containerPort: 80
name: http
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3(opis sondy w liniach 13-20)
volumes w obiekcie specvolumeMounts w każdym kontenerze, w którym
chcemy użyć woluminuCzasami potrzebujemy katalogu, w którym cache’owane będą pośrednie wyniki
Wyniki te zawsze można odtworzyć, więc nie potrzebujemy trwałego woluminu
Z drugiej strony, w razie restartu kapsuły na tym samym węźle chcielibyśmy móc wykorzystać cache
W takiej sytuacji przydają się woluminy nietrwałe takie jak
emptyDir (pusty przy pierwszym uruchomieniu, ale
utrzymujący zawartość w razie restartu)
emptyDir jest przydatny również w sytuacji gdy różne
kontenery z tej samej kapsuły wymagają współdzielenia plików
Przykład:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
volumes:
- name: nginx-volume
emptyDir: {}
containers:
- image: nginx
name: nginx-container
ports:
- containerPort: 80
name: http
protocol: TCP
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-volume(opis woluminu w liniach 6-9, a jego konfiguracja w liniach 16-18)
# skopiuj index.html
kubectl cp index.html nginx-pod:/usr/share/nginx/html/
# wymuś restart kontenera w kapsule
kubectl exec nginx-pod -- /sbin/killall5
# włącz przekierowanie portu, by sprawdzić, że index.html wciąż istnieje
kubectl port-forward nginx-pod 8080:80Jeżeli aplikacja przetwarza dane, które mają pozostać nawet w przypadku usunięcia wszystkich kapsuł lub przeniesienia kapsuł na inne węzły, to należy stworzyć wolumin trwały
PersistentVolume to osobny rodzaj obiektu, który
tworzymy niezależnie od kapsuł:
cat nginx-persistent-volume.ymlapiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-persistent-volume
spec:
storageClassName: nginx-storage
capacity:
storage: 100Mi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/persistent-volume"kubectl apply -f nginx-persistent-volume.ymlstorageClassName to nazwa, dzięki której będzie
można wykorzystać obiekt w kapsule
Aby sprawdzić dostępne trwałe woluminy:
kubectl get persistentvolumesNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nginx-persistent-volume 100Mi RWO Retain Available nginx-storage 9m5sAby korzystać z trwałego woluminu, należy utworzyć obiekt typu
PersistentVolumeClaim:
cat nginx-persistent-volume-claim.ymlapiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-persistent-volume-claim
spec:
storageClassName: nginx-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mikubectl apply -f nginx-persistent-volume-claim.ymlMożemy teraz wykorzystać w kapsule wolumin typu
persistentVolumeClaim:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
volumes:
- name: nginx-persistent-volume
persistentVolumeClaim:
claimName: nginx-persistent-volume-claim
containers:
- image: nginx
name: nginx-container
ports:
- containerPort: 80
name: http
protocol: TCP
volumeMounts:
- mountPath: /tmp/testing
name: nginx-persistent-volumeEtykiety
to pary klucz=wartość, które możemy przypisywać obiektom
Kubernetes np. app.version=x.y.z
Można je następnie wykorzystać do filtrowania obiektów
korzystając ze składni --selector=:
key!keykey=valuekey!=valuekey in (v1, v2)key notin (v1, v2)Adnotacje
to również pary klucz=wartość, ale służą zapisaniu pewnych
dodatkowych informacji, wg których obiekty nie będą filtrowane np. URL
do repozytorium, znaczniki czasu, itp.
Przykład:
Usługi pozwalają na reprezentację grupy kapsuł jako spójnego obiektu
Grupa kapsuł tworzących usługę określana jest poprzez wspólny dla nich selektor
Usługa otrzymuje własny adres IP (zwany adres IP klastra), który jest wykorzystywany do równoważenia obciążenia wszystkich kapsuł
Przypisany adres IP dostaje także wpis w DNS, zatem można się do niego odwoływać po nazwie w innych częściach aplikacji
Przykład:
cat nginx-service.yamlapiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80port to numer portu, na jakim usługa będzie dostępna w
klastrzetargetPort to numer portu, na jakim nasłuchują kapsuły
zgrupowane w ramach tej usługikubectl apply -f nginx-service.yaml$ kubectl exec -it nginx-pod -- bash
root@nginx-pod:/# host -a nginx-service
Trying "nginx-service.default.svc.cluster.local"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48398
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;nginx-service.default.svc.cluster.local. IN ANY
;; AUTHORITY SECTION:
cluster.local. 30 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1621158098 7200 1800 86400 30
Received 150 bytes from 10.96.0.10#53 in 0 msPełna nazwa domenowa to
nginx-service.default.svc.cluster.local:
nginx-service to nazwa usługidefault to przestrzeń nazw, w której znajduje
się usługasvc oznacza, że wpis opisuje usługę, a nie inny typ
obiektucluster.local domyślna nazwa podstawowaKonfiguracja DNS w kapsule jest przygotowana tak, że można używać
po prostu nazwy nginx-service
Powyższa konfiguracja nie zawiera konfiguracji
spec.type, więc domyślną wartością jest
ClusterIP
ClusterIP oznacza, że usługa ta będzie dostępna
wewnątrz klastra jako wewnętrzny-ip:port
Aby udostępnić funkcjonalność na zewnątrz, należy
zmienić spec.type na wartość NodePort
Domyślnie Kubernetes dla każdego portu w tablicy
spec.ports wybiera losową wartość nodePort z
przedziału 30000-32767 i udostępnia usługę typu
NodePort zarówno jako wewnetrzny-ip:port oraz
ip-wezla:nodePort
Można jednak wyłączyć losowość i samodzielnie
podać wartość nodePort
Na początku stworzyliśmy klaster, który przekazuje
ip-wezla:30000 na 0.0.0.0:8080, a zatem możemy
usłudze przypisać nodePort na wartość 30000:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000(zwróć uwagę na linie 6 i 13)
Po uruchomieniu takiej usługi, będziemy mogli sprawdzić jej działanie przechodząc w przeglądarce na adres http://localhost:8080