Certified Kubernetes Administrator (CKA) krok po kroku – część 5

W jaki sposób można w środowisku skonteneryzowanym utrzymywać dane i stan ?

 

Jak wiadomo każdy kontener ma swój system plików dostępny tylko dla niego. Ten system jest ulotny (efemeryczny). Oznacza to, ze wszelki dane jakie zapiszemy w kontenerze podczas jego pracy po ponownym uruchomienia będą utracone.Jeżeli mamy do czynienia z aplikacja, która działa samodzielnie i nie musi zapisywać swojego stanu nie mamy problemu, ale co zrobić w sytuacji, gdy aplikacja składa się z wielu kontenerów i musi wymieniać swój stan i utrzymywać go między restartami?

 

O tym jest dzisiejsza opowieść:

 

Wolumeny danych

 

Na początku pracy z Kubernetes pewną trudność sprawiała mi budowa manifestów, które wykorzystują wolumeny. Nie uczmy się na pamięć, wykorzystajmy zarówno system pomocy jak i wbudowane narzędzia, niekoniecznie zgodnie z intencją ich twórców.  Jak można  sprawnie budować takie manifesty?

Na początku zacznijmy od prostego ćwiczenia

Czym różnią się dwa tryby kubectl –dry-run ? (client, server)

W przypadku opcji client polecenie kubectl wyświetli tylko obiekt ( dla opcji -o yaml) , jaki zostanie wysłany do klastra, a w przypadku opcji server obiekt zostanie wysłany na klaster, zostaną dopisane różne domyślne wartości, ale bez końcowego zapisu obiektu.

Przykładowe polecenie generujące manifest obiektu pod.

kubectl run test --image=nginx.alpine -o yaml --dry-run=client

Zwrotny manifest wygląda w taki sposób

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: test
  name: test
spec:
  containers:
  - image: nginx.alpine
    name: test
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

 

W  wersji “serwerowej” wygląda to nieco inaczej.

kubectl run test --image=nginx:alpine -o yaml --dry-run=server 

Manifest zawiera w sobie o wiele więcej danych. Zwróćmy uwagę na jeden aspekt jakim jest podłączenie za pomocą obiektu secret tokena pochodzącego od konta serwisowego (serviceaccount) o nazwie default. Każda przestrzeń nazw ma utworzoną parę obiektów konto serwisowe  (serviceaccount) o nazwie default i skojarzony z tym kontem token zawarty w obiekcie secret default-token-HASH.

 

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-06-17T20:03:21Z"
  labels:
    run: test
 # managedfields ommited (...)
  name: test
  namespace: default
  selfLink: /api/v1/namespaces/default/pods/test
  uid: 2049eb41-0974-4017-b5b6-1eddc29fbd9a
spec:
  containers:
  - image: nginx:alpine
    imagePullPolicy: IfNotPresent
    name: test
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-hzgn6
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: default-token-hzgn6
    secret:
      defaultMode: 420
      secretName: default-token-hzgn6
status:
  phase: Pending
  qosClass: BestEffort

 

Zobaczmy jak to wygląda w naszym przypadku. Wylistujmy wszystkie obiekty kont serwisowych i obiekty secret w przestrzeni nazw default

kubectl get sa,secret -n default
NAME SECRETS AGE
serviceaccount/default 1 8m17s

NAME TYPE DATA AGE
secret/default-token-hzgn6 kubernetes.io/service-account-token 3 8m17s
kubectl get sa default -o yaml -n default
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2021-06-17T19:57:18Z"
  name: default
  namespace: default
  resourceVersion: "380"
  selfLink: /api/v1/namespaces/default/serviceaccounts/default
  uid: 9e41fc13-3d63-4b10-ac6c-f1cd32d2a643
secrets:
- name: default-token-hzgn6

 

Nazwa obiektu secret dla konta serwisowego jest zawarta w mapie secrets.

 

Wracając do tematu podłączania do kontenera wolumenu, to warto zauważyć, że lista volumenów jest na poziomie spec. całego obiektu pod, natomiast podłączenie tego wolumenu jest już na poziomie kontenera w obiekcie pod, czyli spec.containers[*].

Przypominam, iż pod jest obiektem grupującym wiele kontenerów i te kontenery umieszczone są w tym samej linuksowej przestrzeni nazw, komunikują się przez localhost i mogą dzielić ten sam wolumen. Wolumeny mają w ramach obiektu pod unikalne nazwy.

Poniżej umieściłem część manifestu na poziomie listy wolumenów

spec:
# (...)
  volumes:
  - name: default-token-hzgn6
    secret:
      defaultMode: 420
      secretName: default-token-hzgn6

Mamy tu do czynienia z jednym elementem listy  na poziomie pod.spec.volumes, gdzie unikalną wartością jest name. Dodatkowo widzimy informację, o tym, że źródłem wolumenu jest obiekt secret. Lista takich źródeł jest dosyć długa i zależy od dostawcy usługi magazynowej (storage).

Patrząc na część manifestu na poziomie miejsca podłączenia (volumeMounts)

spec:
  containers:
# (...)
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-hzgn6
      readOnly: true

W tym przypadku na poziomie pod.spec.containers[*].volumeMounts widzimy tę samą wartość name co wcześniej i mamy dodany punkt podłączenia (mountPath) w kontenerze. W naszym przypadku jest to katalog wewnątrz kontenera o ścieżce /var/run/secrets/kubernetes.io/serviceaccount z ustawionym dodatkowo znacznikiem trybu tylko do odczytu (readOnly: true).

 

Część manifestu obrazującego oba poziomy dotyczące wolumenów.

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod
  #  managedfields ommited (...)
  name: pod
spec:
  containers:
# (...)
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-hzgn6
      readOnly: true
#  (...)
  volumes:
  - name: default-token-hzgn6
    secret:
      defaultMode: 420
      secretName: default-token-hzgn6

 

Jak wygląda nasz obiekt secret default-token-hzgn6 ?

 

kubectl get secret default-token-hzgn6
NAME                  TYPE                                  DATA   AGE
default-token-hzgn6   kubernetes.io/service-account-token   3      12m

 

Jak widać obiekt secret zawiera trzy pary klucz:wartość

 

kubectl get secret default-token-hzgn6 -o yaml

Obrobiony manifest wygląda tak:

 

apiVersion: v1
data:
  ca.crt: REDACTED
  namespace: ZGVmYXVsdA==
  token: REDACTED
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: 9e41fc13-3d63-4b10-ac6c-f1cd32d2a643
  creationTimestamp: "2021-06-17T19:57:18Z"
  # managedFields ommitted 
    manager: kube-controller-manager
    operation: Update
    time: "2021-06-17T19:57:18Z"
  name: default-token-hzgn6
  namespace: default
  resourceVersion: "379"
  selfLink: /api/v1/namespaces/default/secrets/default-token-hzgn6
  uid: 8ed2d192-b058-4147-b811-6e43a7bc2824
type: kubernetes.io/service-account-token

 

Dane , które  można przekazać do kontenera są na poziomie data.

data: 
  ca.crt: REDACTED
  namespace: ZGVmYXVsdA==
  token: REDACTED

 

Wdróżmy nasz obiekt na klaster

kubectl run test --image=nginx:alpine
pod/test created

 

Spróbujmy wejść do środka kontenera w obiekcie o nazwie test. Wykorzystamy do tego polecenie kubectl exec

kubectl exec test -it -- sh

To co widzimy w środku naszego kontenera

# zmieniamy katalog

/ cd /var/run/secrets/kubernetes.io

# listujemy pliki ktore w nim są

/run/secrets/kubernetes.io # ls -la
total 8
drwxr-xr-x 3 root root 4096 Jun 17 20:13 .
drwxr-xr-x 3 root root 4096 Jun 17 20:13 ..
drwxrwxrwt 3 root root 140 Jun 17 20:12 serviceaccount

# wyswietlamy zawartosc podkatalogu serviceaccount
/run/secrets/kubernetes.io # ls -la serviceaccount/
total 4
drwxrwxrwt 3 root root 140 Jun 17 20:12 .
drwxr-xr-x 3 root root 4096 Jun 17 20:13 ..
drwxr-xr-x 2 root root 100 Jun 17 20:12 ..2021_06_17_20_12_55.229534384
lrwxrwxrwx 1 root root 31 Jun 17 20:12 ..data -> ..2021_06_17_20_12_55.229534384
lrwxrwxrwx 1 root root 13 Jun 17 20:12 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Jun 17 20:12 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Jun 17 20:12 token -> ..data/token

# wyswietlamy zawartość pliku namespace

/run/secrets/kubernetes.io # cat serviceaccount/namespace 

default

Mamy plik manifestu w obiekcie typu secret zawierający trzy pary klucz:wartość. W prosty sposób możemy podłączyć te dane jako trzy osobne pliki. Takie pliki traktowane są przez system plikowy kontenera w trybie do odczytu. Co więcej zmiana obiektu secret powoduje automatyczną zmianę podłączonego pliku (dla danej pary klucz: wartość) bez restartu obiektu pod.

Uważna osoba zada na pewno pytanie, po co montujemy niejako w tle takie rzeczy. Otóż obiekt pod uruchomiony na klastrze Kubernetes ma dostęp do Kubernetes API. Uprawnienia z jakimi ma do tego API dostęp wynikają z uprawnień jakie ma  konto serwisowe (w naszym przypadku o nazwie default)  w zadanej przestrzeni nazw.  Nie jest to zagadnienie zakresu egzaminacyjnego, ale warto wiedzieć, że jeśli nie potrzebujemy dostępu do API Kubernetes, to możemy zablokować podłączenie danych związanych z obiektem secret stowarzyszonym z danym kontem serwisowym (serviceaccount). Warto dokładniej poczytać o RBAC.

https://kubernetes.io/docs/reference/access-authn-authz/rbac/

 

Mamy do dyspozycji dwie metody, które zablokują dostęp do API Kubernetes:

 

Można  wyłączyć dla danego konta możliwość korzystania z API Kubernetes dodając do manifestu  automountServiceAccountToken: false

 

apiVersion: v1
kind: ServiceAccount
metadata:
  name: no-token-please
  namespace: default
automountServiceAccountToken: false

 

Można też w definicji np. obiektu typu pod przy definicji konta serwisowego poprosić o niepodłączanie do API dodając automountServiceAccountToken: false

 

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  serviceAccountName: yes-token-please
  automountServiceAccountToken: false

 

 

Zniecierpliwiona osoba, zacznie się pytać, po co tyle opowieści o korzystaniu z API i podłączaniu plików do kontenera. Niniejszy wpis nie jest częścią jakiegoś kursu Kubernetes, których jest już dużo i w których można znaleźć więcej informacji. Jest to próba pomocy dla osób, które przygotowują się do certyfikacji CKA, ale jednocześnie nie chciałbym, aby był to jedynie spis gotowych rozwiązań, które co prawda pozwolą wyrobić sobie manualną sprawność, ale niewiele więcej.

 

W jaki sposób Kubernetes jest w stanie zapewnić nam przechowanie danych pomimo restartu kontenera ?

Dotarliśmy do początku naszej opowieści.  Przypomnę obrazek z jednej z poprzednich części

 

Tym razem zajmiemy się częścią prawą (zieloną) zarówno dla obiektu configmap jak i dla obiektu secret, z tym, że dla obiektu secret zamiast mapy configMap będzie mapa secret.

Kubernetes obsługuje ponad 20 typów wolumenów, nie ma sensu uczyć się ich na pamięć.

Wolumeny ulotne

 

EmptyDir

Wolumen typu emptyDir jest tworzony w momencie, gdy na danym węźle pojawia się obiekt pod po raz pierwszy. Wolumen ten istnieje tak długo jak obiekt pod na danym węźle. Zgodnie z nazwą początkowy stan jest pusty. Wszystkie kontenery w danym obiekcie pod mogą czytać i pisać do tego samego wolumenu. Kiedy pod jest zrestartowany lub usunięty dane w tego typu wolumenie są tracone.

Jakie  jest główne zastosowanie?

  • cache
  • długotrwałe obliczenia, które muszą być wykonane w pamięci
  • dane tymczasowe na przykład podczas sortowania
Przykładowy manifest wykorzystujący emptyDir
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - image: my-app-image
    name: my-app
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}
Uwaga: Tego typu wolumenów nie należy używać do zapisu trwałych zmian.

 

HostPath

Wolumen typu hostPath pozwala na podłączenie pliku lub katalogu znajdującego się na węźle, na którym został uruchomiony pod. Można ustalić, czy plik lub katalog musi wcześniej istnieć, czy też ma być utworzony podczas startu obiektu pod.

type: Directory oznacza, że katalog o tej nazwie musi istnieć na węźle. Należy go utworzyć wcześniej,

type: DirectoryOrCreate oznacza, że katalog o tej  nazwie zostanie utworzony jeśli nie istnieje na węźle,

type: File oznacza, że plik o tej nazwie musi istnieć na węźle. Należy go utworzyć wcześniej,

type: FileOrCreate oznacza, że plik o tej  nazwie zostanie utworzony jeśli nie istnieje na węźle,

 

Jakie  jest główne zastosowanie?

Uruchamianie kontenerów, które wymagają dostępu do wewnętrznych mechanizmów Docker. Można wykorzystać ścieżkę hostPath /var/lib/docker

Uruchamianie wewnątrz kontenera cAdvisor Można wykorzystać ścieżkę hostPath /sys

Wady tego typu rozwiązania:

  • Obiekty pod utworzone z tego samego szablonu mogą się różnie zachowywać na różnych węzłach w zależności od zawartości scieżki hostPath
  • Pliki i katalogi tworzone z hostPath na poziomie węzła są zapisywalne tylko z uprawnieniami użytkownika root. Czyli albo musimy uruchamiać takie obiekty z uprawnieniami root, albo modyfikować uprawnienia do plików/katalogów na poziomie hosta. Oba przypadki prowadzą do problemów z bezpieczeństwem i powinny być stosowane w bardzo uzasadnionych przypadkach.
  • Jeżeli obiekt pod jest częścią obiektu StatefulSet nie może korzystać z hostPath.

 

Przykładowy manifest wykorzystujący hostPath

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - image: my-app-image
    name: my-app
    volumeMounts:
      - mountPath: /test-pd
        name: test-volume
  volumes:
  - name: test-volume 
    hostPath:
    path: /data  #directory on host
    type: Directory #optional

Wolumeny trwałe

 

Najpopularniejsze typy wolumenów, z którymi można się spotkać

 

  • configMap:  Możliwość podłączenia plików zawierających dane niewrażliwe na podstawie zawartości obiektu typu configMap
  • secret: Możliwość podłączenia plików zawierających dane wrażliwe na podstawie zawartości obiektu typu secret
  • persistentVolumeClaim: Możliwość podłączenia obiektu typu persistentVolume do obiektu typu pod za pomocą obiektu  persistentVolumeClaim

 

Umówmy ten ostatni typu wolumenu.

 

Persistent Volumes

 

Obiekty PersistentVolume (PV) mogą powstawać statycznie na podstawie manifestów wdrażanym na klaster przez jego administratora lub dynamicznie na podstawie wskazania obiektu StorageClass. Obiekty StorageClass (SC) zawiera predefiniowaną konfigurację dostarczyciela (provider) i parametry, na podstawie których ma powstać obiekt PersistentVolume. Obiekty te maja zasięg w całym klastrze i nie są umieszczane w przestrzeni nazw.

 

Obiekt PersistemVolumeClaim (PVC) jest żądaniem podłączenia dysku przez uzytkownika, który prosi o obiekt typu PersistentVolume na podstawie wielkości, trybu dostępu do danych, itp. Jeżeli znajdzie się wolny wolumen PV to zostanie on dowiązany (bound) do obiektu PVC. Obiekt PV nie jest bezpośrednio powiązany z obiektem typu pod, ale wykorzystuje jako pośrednika obiekty typu PVC. Obiekty PVC w przeciwieństwie do VP są umieszczane w przestrzeni nazw.

 

PersistentVolume może mieć następujące tryby dostępu do danych (access mode) :

  • ReadWriteOnce –  wolumen może być podłączony w trybie zapisu (read-write) tylko przez jeden obiekt
  • ReadOnlyMany –  wolumen może być podłączony w trybie odczytu (read-only) przez wiele obiektów
  • ReadWriteMany – wolumen może być podłączony w trybie zapisu (read-write) przez wiele obiektów

Tryby te są podczas korzystania z kubectl skracane do

  • RWO – ReadWriteOnce
  • ROX – ReadOnlyMany
  • RWX – ReadWriteMany
A graphic showing how pods connect to persistent volumes and external physical storage.
Powyższy obraz pokazuje w uproszczony sposób powiązanie obiektów typu pod, persistentVolumeClaim i PersistentVolume.
Zabierzmy się za ćwiczenia.

Ćwiczenia

 

Zadanie pierwsze

 

Utwórz obiekt typu pod o nazwie  secret-test zawierający obraz nginx:latest pracujący na porcie 80 .

W kontenerze należy podłączyć obiekt secret o nazwie test-secret w katalogu /etc/secret-volume. Nazwa wolumenu to secret-volume

Umieść go w przestrzeni nazw delta. Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Manifest tworzący obiekt secret wygląda tak:

apiVersion: v1
kind: Secret
metadata:
  name: test-secret
  namespace: delta
data:
  username: bXktYXBw
  password: Mzk1MjgkdmRnN0pi

Obiekt  można wdrożyć na klaster m.in w ten sposób:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: test-secret
  namespace: delta
data:
  username: bXktYXBw
  password: Mzk1MjgkdmRnN0pi
EOF

 

To od czego powinniśmy zacząć to przygotowanie manifestu obiektu pod. Robiliśmy to już wiele razy

kubectl run secret-test --image=nginx:latest --port=80 --dry-run=client -o yaml > 01.pod.secret-test.yaml

W pliku dodajemy brakującą przestrzeń nazwa delta i usuwamy zbędne dane. Manifest powinien wyglądać tak:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: secret-test
  name: secret-test
  namespace: delta
spec:
  containers:
  - image: nginx:latest
    name: secret-test
    ports:
    - containerPort: 80
  dnsPolicy: ClusterFirst
  restartPolicy: Always

Teraz zabieramy się za brakujący wolumen

Dodajemy go w dwóch miejscach:

  • na poziomie specyfikacji całego obiektu pod
apiVersion: v1
kind: Pod
metadata:
  name: secret-test
  namespace: delta
spec:
 # (...)
  volumes:
  - name: secret-volume
    secret:
      secretName: test-secre

Tutaj zawsze będziemy mieli do czynienia z listą wewnątrz volumes.

  1. name (nazwa jest unikalna w obrębie obiektu pod)
  2. typ wolumenu (w naszym przypadku jest to secret), a potem w zależności od typu wolumenu wewnętrzne mapy. W naszym przypadku jest to secretName wskazujący na nazwę obiektu secret.

 

  • na poziomie specyfikacji kontenera w obiekcie pod
apiVersion: v1
kind: Pod
metadata:
  name: secret-test
  namespace: delta
spec:
  containers:
    - name: test-container
   # (...)
      volumeMounts:
      - name: secret-volume
        mountPath: /etc/secret-volume

Tutaj zawsze będziemy mieć do czynienia z listą wewnątrz volumeMounts:

  1. name (nazwa jest unikalna w obrębie obiektu pod), który musi być wyszczegolniony na poziomie spec.volumes. Wiązane wolumenu z punktem podłączenia jest na podstawie nazwy tego wolumenu.
  2. mountPath ( wskazuje miejsce, w którym zostanie podłączony nasz wokumen w kontenerze)

 

Po uwzględnieniu obu części nasz manifest powinien wyglądać tak:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test
  namespace: delta
spec:
  containers:
    - name: test-container
      image: nginx:latest
      ports: 
      - containerPort: 80
      volumeMounts:
      - name: secret-volume
        mountPath: /etc/secret-volume
  volumes:
  - name: secret-volume
    secret:
      secretName: test-secret

 

Wdrażamy obiekt na klaster

kubectl apply -f 01.pod.secret-test.yaml 
pod/secret-test created

 

Zadanie drugie

 

Utwórz obiekt typu pod o nazwie  configmap-test zawierający obraz nginx:latest pracujący na porcie 80 .

W kontenerze należy podłączyć obiekt configmap o nazwie test-configmap w katalogu /etc/configmap. Nazwa wolumenu to configmap-volume

Umieść go w przestrzeni nazw delta. Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Manifest tworzący obiekt configmap wygląda tak:

apiVersion: v1
kind: ConfigMap
metadata:
  name: test-configmap
  namespace: delta
data:
  SPECIAL_LEVEL: very
  SPECIAL_TYPE: charm

 

Tak jak poprzednio obiekt można wdrożyć na klaster za pomocą kubectl apply -f –

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-configmap
  namespace: delta
data:
  SPECIAL_LEVEL: very
  SPECIAL_TYPE: charm
EOF

Standardowo zaczynamy od wygenerowania bazowego obiektu pod

kubectl run configmap-test --image=nginx:latest --port=80 --dry-run=client -o yaml > 02.pod.configmap-test.yaml

 

Po podaniu części związanych z wolumenem pochodzącym z obiektu configmap manifest powinien wyglądać tak:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: configmap-test
  name: configmap-test
  namespace: delta
spec:
  containers:
  - image: nginx:latest
    name: configmap-test
    ports:
    - containerPort: 80
    volumeMounts:
    - name: configmap-volume
      mountPath: /etc/configmap
  volumes:
  - name: configmap-volume 
    configMap: 
      name: test-configmap

 

Wdrażamy obiekt na klaster

kubectl apply -f 02.pod.configmap-test.yaml
pod/configmap-test created

Zobaczmy co możemy zobaczyć w środku kontenera za pomocą polecenia kubectl exec

kubectl exec configmap-test -n delta -it -- bash

 

root@configmap-test:/# ls -la /etc/configmap/
total 12
drwxrwxrwx 3 root root 4096 Jun 20 11:59 .
drwxr-xr-x 1 root root 4096 Jun 20 11:59 ..
drwxr-xr-x 2 root root 4096 Jun 20 11:59 ..2021_06_20_11_59_16.816891944
lrwxrwxrwx 1 root root   31 Jun 20 11:59 ..data -> ..2021_06_20_11_59_16.816891944
lrwxrwxrwx 1 root root   20 Jun 20 11:59 SPECIAL_LEVEL -> ..data/SPECIAL_LEVEL
lrwxrwxrwx 1 root root   19 Jun 20 11:59 SPECIAL_TYPE -> ..data/SPECIAL_TYPE
root@configmap-test:/# cat /etc/configmap/SPECIAL_LEVEL 
very

root@configmap-test:/# cat /etc/configmap/SPECIAL_TYPE
charm

 

Po wykonaniu polecenia  ls /etc/configmap/

Otrzymamy wówczas listę plików

SPECIAL_LEVEL SPECIAL_TYPE

 

Jak widać są to nazwy kluczy  z obiektu configmap o nazwie test-configmap. A zawartością każdego z tych plików jest wartość związana z danym kluczem.

 

Uważna osoba zauważy pewną niekonsekwencję nazewnictwa pól.

W jednym przypadku użyliśmy name (dla obiektu configMap) a w drugim secretName (dla obiektu secret).

W razie wątpliwości można skorzystać z wbudowanej pomocy

kubectl explain pod.spec.volumes.secret --recursive
KIND:     Pod
VERSION:  v1

RESOURCE: secret <Object>

DESCRIPTION:
     Secret represents a secret that should populate this volume. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#secret

     Adapts a Secret into a volume. The contents of the target Secret's Data
     field will be presented in a volume as files using the keys in the Data
     field as the file names. Secret volumes support ownership management and
     SELinux relabeling.

FIELDS:
   defaultMode  <integer>
   items        <[]Object>
      key       <string>
      mode      <integer>
      path      <string>
   optional     <boolean>
   secretName   <string>

 

kubectl explain pod.spec.volumes.configMap --recursive

 

KIND:     Pod
VERSION:  v1

RESOURCE: configMap <Object>

DESCRIPTION:
     ConfigMap represents a configMap that should populate this volume

     Adapts a ConfigMap into a volume. The contents of the target ConfigMap's
     Data field will be presented in a volume as files using the keys in the
     Data field as the file names, unless the items element is populated with
     specific mappings of keys to paths. ConfigMap volumes support ownership
     management and SELinux relabeling.

FIELDS:
   defaultMode  <integer>
   items        <[]Object>
      key       <string>
      mode      <integer>
      path      <string>
   name <string>
   optional     <boolean>

 

Nie uczymy się na pamięć. Wbudowana w narzędzie kubectl pomoc może nam znacznie ułatwić pracę.

 

Zadanie trzecie

 

Utwórz obiekt typu pod o nazwie  configmap-test-key zawierający obraz nginx:latest pracujący na porcie 80 .

W kontenerze należy podłączyć obiekt configmap o nazwie test-configmap w katalogu /etc/configmap, z tego obiektu należy wykorzystać jedynie klucz SPECIAL_LEVEL, który powinien być umiezczony w  Nazwa wolumenu to configmap-volume-key

Umieść go w przestrzeni nazw delta. Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Zadanie wygląda na bardzo podobne do poprzedniego. Zacznijmy od skopiowania manifestu

cp 02.pod.configmap-test.yaml 03.pod.configmap-test-key.yaml

 

Podczas edycji pliku należy zmienić nazwę obiektu pod, nazwę wskazanego obiektu configmap.

apiVersion: v1
kind: Pod
metadata:
  labels: 
    run: configmap-test-key 
  name: configmap-test-key
  namespace: delta
spec:
  containers:
    - name: configmap-test-key
      image: nginx:latest
      volumeMounts:
      - name: configmap-volume-key
        mountPath: /etc/configmap
  volumes:
    - name: configmap-volume-key
      configMap:
        name: test-configmap
        items:
        - key: SPECIAL_LEVEL
          path: keys

 

Podczas edycji pliku należy zmienić nazwę obiektu pod, nazwę wskazanego obiektu configmap.

spec:
 # (...)
  volumes:
    - name: configmap-volume-key
      configMap:
        name: test-configmap
        items:
        - key: SPECIAL_LEVEL
          path: keys

Ponieważ nie chcemy korzystać z całości danych zawartych w obiekcie configmap w naszej definicji wolumenu pojawiły się dodatkowe elementy listy.  Dane wartości klucza o nazwie SPECIAL_KEY zostaną umieszczone w pliku o nazwie keys, który zostanie umieszczony w katalogu  mountPath: /etc/configmap.

https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

 

Wdrażamy obiekt na klaster

kubectl apply -f 01.pod.secret-test.yaml 
pod/secret-test created

Zobaczmy co możemy zobaczyć weanątrz kontenera:

kubectl exec configmap-test-key -n delta -it -- sh

W środku kontenera proponuję wykonać polecenie cat /etc/configmap/keys

ls -la /etc/configmap/
total 12
drwxrwxrwx 3 root root 4096 Jun 20 11:53 .
drwxr-xr-x 1 root root 4096 Jun 20 11:53 ..
drwxr-xr-x 2 root root 4096 Jun 20 11:53 ..2021_06_20_11_53_34.366829492
lrwxrwxrwx 1 root root   31 Jun 20 11:53 ..data -> ..2021_06_20_11_53_34.366829492
lrwxrwxrwx 1 root root   11 Jun 20 11:53 keys -> ..data/keys

root@configmap-test-key:/# cat /etc/configmap/keys 
very

otrzymamy wówczas zawartość tego pliku:

very

 

Jak widać, w środku wskazanej do podmontowania ścieżki pojawił się plik o nazwie keys zawierający wartość klucza SPECIAL_KEY.

 

Następne zadanie jest na rozgrzewkę.

 

Zadanie  czwarte

 

Utwórz obiekt typu pod o nazwie  pod-webapp zawierający obraz nginx:latest pracujący na porcie 80 . Umieść go w przestrzeni nazw delta. Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

kubectl run webapp --image=nginx --port=80 --dry-run=client -o yaml > 04.pod.pod-webapp.yaml

Masz manifest powinien wyglądać mniej wiecej tak. Pamiętajmy o dodawaniu przestrzeni nazw, w tym przypadku delta.

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod-webapp
  name: pod-webapp
  namespace: gamma
spec:
  containers:
  - image: nginx
    name: webapp
    ports:
    - containerPort: 80
  dnsPolicy: ClusterFirst
  restartPolicy: Always

 

Wdrażamy nasz obiekt na klaster

kubectl apply -f 04.pod.pod-webapp.yaml

Zadanie piąte

 

Utwórz obiekt typu pod o nazwie  pod-webapp-volume zawierający obraz nginx:latest pracujący na porcie 80 . Umieść go w przestrzeni nazw delta.

Do kontenera należy podłączyć wolumen o nazwie nginx-volume typu emptyDir, który będzie podłączony w kontenerze nginx pod ścieżką /opt/data/.

Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Wszystkie kluczowe parametry są poniżej

 

Pod name:  pod-webapp-volume
Volume Name: nginx-volume
Image Name: nginx:latest
Container port: 80
Volume Type: emptyDir()
Volume Mount: /opt/data/

 

Najłatwiej będzie wykorzystać istniejący juz plik manifestu z poprzedniego zadania

cp 04.pod.pod-webapp.yaml 05.pod.pod-webapp-volume.yaml

 

Zmieniamy nazwę obiektu pod z pod-webapp na pod-webapp-volume i dodajemy wolumen o nazwie nginx-volume.

Nasz manifest powinien wyglądać tak:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod-webapp-volume
  name: pod-webapp-volume
  namespace: delta
spec:
  containers:
  - image: nginx:latest
    name: webapp-volume
    ports:
    - containerPort: 80
    resources: {}
    volumeMounts:
    - name: nginx-volume
      mountPath: /opt/data
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: nginx-volume
    emptyDir: {}
spec:
  # (...)
  volumes:
  - name: nginx-volume
    emptyDir: {}

Czym to się właściwie różni od wolumenów z poprzednich zadań ?

Zawsze będziemy mieli do czynienia z listą wewnątrz volumes.

  1. name (nazwa jest unikalna w obrębie obiektu pod)
  2. typ wolumenu (w naszym przypadku jest to emptyDir), a potem w zależności od typu wolumenu wewnętrzne mapy. W naszym przypadku jest to wartość pusta, czyli {}

 

Wdrażamy nasz obiekt na klaster

kubectl apply -f 05.pod.pod-webapp-volume.yaml

 

Zadanie szóste

 

Utwórz obiekt typu pod o nazwie  pod-webapp-volume-host zawierający obraz nginx:latest pracujący na porcie 80 . Umieść go w przestrzeni nazw delta.

Do kontenera należy podłączyć wolumen o nazwie webapp-host typu hostPath wskazujący na ścieżkę/var/log/nginx/, który będzie podłączony w kontenerze nginx pod ścieżką /var/log/nginx/ Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Wszystkie kluczowe parametry są poniżej

Pod name: pod-webapp-volume-host
Volume Name: webapp-host
Image Name: nginx:latest
Container port: 80
Volume Type: HostPath /var/log/nginx/
Volume Mount: /var/log/nginx/

 

Po raz kolejny wykorzystamy istniejący juz plik manifestu z poprzedniego zadania

cp 05.pod.pod-webapp-volume.yaml 06.pod.pod-webapp-volume-host.yaml

Zmieniamy nazwę obiektu pod i nazwę wolumenu (w dwóch miejscach)

Masz plik manifestu powinien wyglądać tak:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod-webapp-volume-host
  name: pod-webapp-volume-host
  namespace: delta
spec:
  containers:
  - image: nginx:latest
    name: webapp-volume-host
    ports:
    - containerPort: 80
    resources: {}
    volumeMounts:
    - name: webapp-host
      mountPath: /var/log/nginx/
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: webapp-host
    hostPath:
      path: /var/log/nginx/

Sprawdzmy czym się to różni w porównaniu z manifestem poprzedniego zadania (pod z volumenem typu emptyDir).

 

Masz plik manifestu powinien wyglądać tak:

spec:
 # (...)
  volumes:
  - name: webapp-host
    hostPath:
      path: /var/log/nginx/

Zawsze będziemy mieli do czynienia z listą wewnątrz volumes.

  1. name (nazwa jest unikalna w obrębie obiektu pod)
  2. typ wolumenu (w naszym przypadku jest to hostPath), a potem w zależności od typu wolumenu wewnętrzne mapy. W naszym przypadku jest to mapa path: /var/log/nginx/

Wdrażamy nasz obiekt na klaster

kubectl apply -f 06.pod.pod-webapp-volume-host.yaml

 

Zadanie siódme

 

Utwórz obiekt typu persistentVolume o nazwie pv-data, który potrafi przechować 50Mi danych w trybie ReadWriteMany. Volumem ma korzystać z hostPath /var/log/data

 

Wszystkie kluczowe parametry są poniżej

    Volume Name: pv-data
    Storage: 50Mi
    Access modes: ReadWriteMany
    Host Path: /var/log/data

 

Tu po raz pierwszy nie możemy zastosować generatora w kubectl. Mamy dwie możliwości .

a) Skorzystać z systemu pomocy kubectl explain pv

b) Sięgnąć do przykładowych manifestów z dokumentacji.

 

Jak wygląda informacja zwrotna dla polecenia

 

kubectl explain persistentvolume.spec --recursive

 

KIND:     PersistentVolume
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec defines a specification of a persistent volume owned by the cluster.
     Provisioned by an administrator. More info:
     https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes

     PersistentVolumeSpec is the specification of a persistent volume.

FIELDS:
   accessModes  <[]string>
   awsElasticBlockStore <Object>
      fsType    <string>
      partition <integer>
      readOnly  <boolean>
      volumeID  <string>
   azureDisk    <Object>
      cachingMode       <string>
      diskName  <string>
      diskURI   <string>
      fsType    <string>
      kind      <string>
      readOnly  <boolean>
   azureFile    <Object>
      readOnly  <boolean>
      secretName        <string>
      secretNamespace   <string>
      shareName <string>
   capacity     <map[string]string>
   cephfs       <Object>
      monitors  <[]string>
      path      <string>
      readOnly  <boolean>
      secretFile        <string>
      secretRef <Object>
         name   <string>
         namespace      <string>
      user      <string>
   cinder       <Object>
      fsType    <string>
      readOnly  <boolean>
      secretRef <Object>
         name   <string>
         namespace      <string>
      volumeID  <string>
   claimRef     <Object>
      apiVersion        <string>
      fieldPath <string>
      kind      <string>
      name      <string>
      namespace <string>
      resourceVersion   <string>
      uid       <string>
   csi  <Object>
      controllerExpandSecretRef <Object>
         name   <string>
         namespace      <string>
      controllerPublishSecretRef        <Object>
         name   <string>
         namespace      <string>
      driver    <string>
      fsType    <string>
      nodePublishSecretRef      <Object>
         name   <string>
         namespace      <string>
      nodeStageSecretRef        <Object>
         name   <string>
         namespace      <string>
      readOnly  <boolean>
      volumeAttributes  <map[string]string>
      volumeHandle      <string>
   fc   <Object>
      fsType    <string>
      lun       <integer>
      readOnly  <boolean>
      targetWWNs        <[]string>
      wwids     <[]string>
   flexVolume   <Object>
      driver    <string>
      fsType    <string>
      options   <map[string]string>
      readOnly  <boolean>
      secretRef <Object>
         name   <string>
         namespace      <string>
   flocker      <Object>
      datasetName       <string>
      datasetUUID       <string>
   gcePersistentDisk    <Object>
      fsType    <string>
      partition <integer>
      pdName    <string>
      readOnly  <boolean>
   glusterfs    <Object>
      endpoints <string>
      endpointsNamespace        <string>
      path      <string>
      readOnly  <boolean>
   hostPath     <Object>
      path      <string>
      type      <string>
   iscsi        <Object>
      chapAuthDiscovery <boolean>
      chapAuthSession   <boolean>
      fsType    <string>
      initiatorName     <string>
      iqn       <string>
      iscsiInterface    <string>
      lun       <integer>
      portals   <[]string>
      readOnly  <boolean>
      secretRef <Object>
         name   <string>
         namespace      <string>
      targetPortal      <string>
   local        <Object>
      fsType    <string>
      path      <string>
   mountOptions <[]string>
   nfs  <Object>
      path      <string>
      readOnly  <boolean>
      server    <string>
   nodeAffinity <Object>
      required  <Object>
         nodeSelectorTerms      <[]Object>
            matchExpressions    <[]Object>
               key      <string>
               operator <string>
               values   <[]string>
            matchFields <[]Object>
               key      <string>
               operator <string>
               values   <[]string>
   persistentVolumeReclaimPolicy        <string>
   photonPersistentDisk <Object>
      fsType    <string>
      pdID      <string>
   portworxVolume       <Object>
      fsType    <string>
      readOnly  <boolean>
      volumeID  <string>
   quobyte      <Object>
      group     <string>
      readOnly  <boolean>
      registry  <string>
      tenant    <string>
      user      <string>
      volume    <string>
   rbd  <Object>
      fsType    <string>
      image     <string>
      keyring   <string>
      monitors  <[]string>
      pool      <string>
      readOnly  <boolean>
      secretRef <Object>
         name   <string>
         namespace      <string>
      user      <string>
   scaleIO      <Object>
      fsType    <string>
      gateway   <string>
      protectionDomain  <string>
      readOnly  <boolean>
      secretRef <Object>
         name   <string>
         namespace      <string>
      sslEnabled        <boolean>
      storageMode       <string>
      storagePool       <string>
      system    <string>
      volumeName        <string>
   storageClassName     <string>
   storageos    <Object>
      fsType    <string>
      readOnly  <boolean>
      secretRef <Object>
         apiVersion     <string>
         fieldPath      <string>
         kind   <string>
         name   <string>
         namespace      <string>
         resourceVersion        <string>
         uid    <string>
      volumeName        <string>
      volumeNamespace   <string>
   volumeMode   <string>
   vsphereVolume        <Object>
      fsType    <string>
      storagePolicyID   <string>
      storagePolicyName <string>
      volumePath        <string>

 

Na początek trochę za dużo informacji, ale wiemy już, że manifest obiektu tego typu powinien zaczynać się tak

 

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-data
spec:

Dodatkowo dla typu hostPath mamy wewnątrz spec.

 

hostPath <Object> 
path <string> type <string>

 

W tym przypadku szybciej będzie skorzystanie z dokumentacji persistent-volumes na stronie produktu, gdzie już pierwszy manifest od góry jest przydatny

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2

Po usunięciu zbędnych informacji nasz plik zgodnie z wymaganiami zadania powninien wyglądać tak:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-data
spec:
  capacity:
    storage: 50Mi
  accessModes:
    - ReadWriteMany
  hostPath:
    path: /var/log/data

 

Wdrażamy nasz obiekt na klaster
kubectl apply -f 07.pv.pv-data.yaml
persistentvolume/pv-data created

 

 

Zadanie ósme

 

Utwórz obiekt typu persistentVolumeClaim o nazwie pvc-log, który ma potrafić przechować 30Mi danych w trybie ReadWriteOnce.

Umieść go w przestrzeni nazw delta.  Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Wszystkie kluczowe parametry są poniżej

    Volume Name: pvc-log
    Storage: 30Mi
    Access modes: ReadWriteOnce
W przypadku tego typu obiektów również nie ma gotowych generatorów, proponuje wycieczkę na stronę
Pierwszy od góry manifest wygląda tak
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}
Po niewielkiej korekcie powinniśmy otrzymać plik o poniższej zawartości
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-log
  namespace: delta
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Mi

 

Wdrażamy nasz obiekt na klaster

kubectl apply -f 08.pvc.pvc-log.yaml
persistentvolumeclaim/pvc-log created

 

W tym momencie warto obejrzeć jak wyglądają nasze obiekty PV oraz PVC.

 

kubectl get pv,pvc -n delta

 

NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
persistentvolume/pv-data   50Mi       RWX            Retain           Available                                   2m13s

NAME                            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc-log   Pending                                                     42s

 

Jak widać mamy obiekt PV o nazwie PV-data, który ma ustawiony status na Available i obiekt typu PVC o nazwie pvc-log, który ma ustawiony status Pending. Jak widać nie doszło do powiązania tych dwóch obiektów ? Dlatego, że oba z nich nie mają wspólnej wartościa dla trybu dostępu (accessModes).

 

Spróbujmy to naprawić w następnym zadaniu.

 

Zadanie dziewiąte

 

Utwórz obiekt typu persistentVolumeClaim o nazwie pvc-log-fix, który ma potrafić przechować 30Mi danych w trybie ReadWriteMany.

Umieść go w przestrzeni nazw delta.  Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Wszystkie kluczowe parametry są poniżej

    Volume Name: pvc-log-fix
    Storage: 30Mi
    Access modes: ReadWritemany
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-log-fix
  namespace: delta
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 30Mi

 

Wdrażamy nasz obiekt na klaster

kubectl apply -f 09.pvc.pvc-log-fix.yaml
persistentvolumeclaim/pvc-log-fix created

 

Warto obejrzeć jak wyglądają nasze obiekty PV oraz PVC.

 

kubectl get pv,pvc -n delta

 

NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pv-data   50Mi       RWX            Retain           Bound    delta/pvc-log-fix                           8m47s

NAME                                STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc-log       Pending                                                      7m16s
persistentvolumeclaim/pvc-log-fix   Bound     pv-data   50Mi       RWX                           70s

 

Jak widać mamy teraz obiekt PV o nazwie pv-data , który ma ustawiony status na Bound ze wskazaniem na PVC o nazwie delta/pvc-log-fix  i i dwa obiekty typu PVC o nazwach pvc-log i pvc-log-fix , z których pierwszy ma ustawiony status Pending a drugi status Bound ze wskazaniem na obiekt PV o nazwie pv-data.

Poprawa trybu dostępu na RWX (czyli ReadWriteMany) pomogła w powiązaniu obiektów PV i PVC.

 

 

Zadanie dziesiąte

 

Utwórz obiekt typu pod o nazwie  pod-webapp-volume-pvc zawierający obraz nginx:latest pracujący na porcie 80 . Umieść go w przestrzeni nazw delta.

Do kontenera należy podłączyć wolumen o nazwie pvc-log-fix typu PersistentVolumeClaim , który będzie podłączony w kontenerze nginx pod ścieżką /var/log/nginx/ Jeżeli przestrzeń nazw nie istnieje należy ją utworzyć.

 

Wszystkie kluczowe parametry są poniżej

 

Name: pod-webapp-volume-pvc

Image Name: nginx:latest
Volume: PersistentVolumeClaim=pvc-log
Volume Mount: /var/log/nginx/
Volume Name: pvc-log-fix
Tutaj możemy skorzystać z manifestu z zadania szóstego

 

cp 06.pod-webapp-volume-host 10.pod.pod-webapp-volume-pvc.yaml
Należy poprawić nazwę obiektu pod i dodatkowo zmodyfikować typ wolumenu z jakim mamy do czynienia
Czyli zamiast
spec:

  volumes:
  - name: webapp-host
    hostPath:
      path: /var/log/nginx/
Powinno być
spec:
 # (...)
  volumes:
  - name: pvc-log
    persistentVolumeClaim:
      claimName: pvc-log-fix
Przy okazji przypominam o systemie pomocy
kubectl explain pod.spec.volumes.persistentVolumeClaim --recursive
KIND:     Pod
VERSION:  v1

RESOURCE: persistentVolumeClaim <Object>

DESCRIPTION:
     PersistentVolumeClaimVolumeSource represents a reference to a
     PersistentVolumeClaim in the same namespace. More info:
     https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

     PersistentVolumeClaimVolumeSource references the user's PVC in the same
     namespace. This volume finds the bound PV and mounts that volume for the
     pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around
     another type of volume that is owned by someone else (the system).

FIELDS:
   claimName    <string>
   readOnly     <boolean>
Nasz plik manifestu powinien wyglądać tak
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod-webapp-volume-pvc
  name: pod-webapp-volume-pvc
  namespace: delta
spec:
  containers:
  - image: nginx:latest
    name: webapp-volume-pvc
    ports:
    - containerPort: 80
    resources: {}
    volumeMounts:
    - name: pvc-log
      mountPath: /var/log/nginx/
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: pvc-log
    persistentVolumeClaim:
      claimName: pvc-log-fix

 

Wdrażamy obiekt na klaster

 

kubectl apply -f 10.pod.pod-webapp-volume-pvc.yaml 
pod/pod-webapp-volume-pvc created

 

Sprawdźmy czy nasz wolumen został podłączony do obiektu pod. Najłatwiej jest to zrobić za pomocą kubectl describe pod

 

kubectl describe pod pod-webapp-volume-pvc -n delta
Volumes:
  pvc-log:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  pvc-log-fix
    ReadOnly:   false
  default-token-g5fdr:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-g5fdr
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  9s    default-scheduler  Successfully assigned delta/pod-webapp-volume-pvc to node01
  Normal  Pulling    9s    kubelet, node01    Pulling image "nginx:latest"
  Normal  Pulled     8s    kubelet, node01    Successfully pulled image "nginx:latest" in 746.348637ms
  Normal  Created    8s    kubelet, node01    Created container webapp-volume-pvc
  Normal  Started    8s    kubelet, node01    Started container webapp-volume-pvc

 

Jak wydać oprócz zadeklarowanego w manifescie wolumenu pojawił się ten związany z obiektem secret umożliwiający dostęp do API Kubernetes, o czy wspominałem na samym początku tego wpisu.

 

Nie zostało opisanych jeszcze wiele aspektów pracy z wolumenami danych, nic nie wspomniałem o reclaimPolicy i nie ma żadnych przykładów dotyczących storageClass, temat jest obszerny i nie chciałem dodatkowo rozszerzać liczby minut do przeczytania i przećwiczenia. Mam nadzieję, że chętni sięgną do dokumentacji.

 

Na tym kończymy dzisiejszą audycję, do usłyszenia wkrótce.

 

Poprzednie części

 

Certified Kubernetes Administrator (CKA) krok po kroku – część 1

Certified Kubernetes Administrator (CKA) krok po kroku &#8211; część 2

Certified Kubernetes Administrator (CKA) krok po kroku – część 3

Certified Kubernetes Administrator (CKA) krok po kroku &#8211; część 4

 

Następne części:

 

TODO

 

Literatura:

 

https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands

https://dev.to/techworld_with_nana/difference-between-emptydir-and-hostpath-volume-types-in-kubernetes-286g

https://kubernetes.io/docs/concepts/configuration/secret/

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistent-volumes

https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

 

Kubernetes (4): Persistent Volumes – Hello World

 

Kubernetes (5) Local Persistent Volumes – A Step-by-Step Tutorial

 

 

 

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.