Tworzenie klastra:
cat kind-cluster.yml
apiVersion: 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.yaml
Sprawdzanie wersji klastra i klienta:
kubectl version
Client 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 nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 21h v1.20.2
Wyświetlanie informacji o węźle:
kubectl describe nodes kind-control-plane
Podstawowe informacje:
Name: kind-control-plane
Roles: control-plane,master
Labels: kubernetes.io/arch=amd64
kubernetes.io/os=linux
kubernetes.io/hostname=kind-control-plane
Stan 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 status
Informacje 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.2
Każ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 yaml
Aby 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.yaml
Ponieważ 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.yaml
Albo:
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.yml
Wypisanie kapsuł:
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 2m
Wypisanie szczegółowych informacji o kapsule:
kubectl describe pods nginx-pod
Name: 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-container
Aby stworzyć tunel z lokalnego komputera, przez węzeł Kubernetes, aż do kapsuły:
kubectl port-forward nginx-pod 8080:80
Aby sprawdzić logi (standardowy strumień wyjścia i błędów):
kubectl logs nginx-pod
Aby uruchomić komendę (np. interaktywny shell):
kubectl exec -it nginx-pod -- bash
Aby 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 spec
volumeMounts
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:80
Jeż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.yml
apiVersion: 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.yml
storageClassName
to nazwa, dzięki której będzie
można wykorzystać obiekt w kapsule
Aby sprawdzić dostępne trwałe woluminy:
kubectl get persistentvolumes
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nginx-persistent-volume 100Mi RWO Retain Available nginx-storage 9m5s
Aby korzystać z trwałego woluminu, należy utworzyć obiekt typu
PersistentVolumeClaim
:
cat nginx-persistent-volume-claim.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-persistent-volume-claim
spec:
storageClassName: nginx-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mi
kubectl apply -f nginx-persistent-volume-claim.yml
Moż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-volume
Etykiety
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
!key
key=value
key!=value
key 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.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
port
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 ms
Peł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