Инструменты пользователя

Инструменты сайта


learning:k8s-cka

https://github.com/sandervanvugt/cka

Установка кластера

Мин. требования к нодам:

  • Современная ОС типа Ubuntu 22.04
  • 2 ГБ памяти
  • 2 vCPU и более для контрольной ноды
  • Все ноды должны видеть друг друга по сети

До установки кластера через kubeadm, нужно поставить

Итак:

git clone https://github.com/sandervanvugt/cka # на всех нодах
cd cka # на всех нодах
sudo ./setup-container.sh # на всех нодах - установить контейнерный движок
sudo ./setup-kubetools.sh # на всех нодах - установить kubetools
 
# Установить кластер
sudo kubeadm init
 
# Настроить клиент (описано в тексте после завершения установки кластера)
To start using your cluster, you need to run the following as a regular user:
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
 
# Проверить работоспособность клиента:
kubectl get all
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6m29s
 
# Установить сетевой аддон (здесь: calico) 
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
 
# присоединить воркеров к кластеру:
sudo kubeadm join 10.1.0.201:6443 --token 654qgh.545455454545454 \
        --discovery-token-ca-cert-hash sha256:234234234234234234234234234
 
# Проверить наличие всех нод
kubectl get nodes
NAME     STATUS   ROLES           AGE   VERSION
t-k8s1   Ready    control-plane   11m   v1.28.2
t-k8s2   Ready    <none>          71s   v1.28.2
t-k8s3   Ready    <none>          67s   v1.28.2
 
# В большинстве случаев достаточно просто kubeadm init, но есть ряд опций, которые могут пригодиться, например,
--apiserver-advertise-address: the IP address on which the API server is listening
--config: the name of a configuration file used for additional configuration
--dry-run: performs a dry-run before actually installing for real
--pod-network-cidr: sets the CIDR used for the Pod network
--service-cidr: sets the service CIDR to something other than 10.96.0.0/12
 
# Справка по опциям
kubeadm init -h |less
 
# Если kubeadm init или kubeadm join отработали криво, то откатить уже внесённые изменения на хосте можно
kubeadm reset
 
# Токен, который выдаётся после инсталляции кластера, временный. Чтобы получить новый для подключения новых нод:
sudo kubeadm token create --print-join-command

Настройка автокомплита и алиаса: https://kubernetes.io/docs/reference/kubectl/quick-reference/#bash

Настройка клиента, контекст

Для получения административного доступа к кластеру через клиента, файл /etc/kubernetes/admin.conf копируется в ~/.kube/config.
Под рутом для той же цели можно выполнить export KUBECONFIG=/etc/kubernetes/admin.conf
Для более тонкой настройки нужно заводить спец. пользователя и настраивать ему права через управление ролями (RBAC).

Контекст - шаблон конфигурации настроек клиента. При выборе контекста активируется определённая группа параметров клиента.

  • Кластер, к которому идёт подключение
  • Пространство имён (namespace)
  • Пользователь
kubectl config view # просмотр контекста
kubectl set-context # задать контекст
kubectl use-context # использовать контекст
 
# Кластер задаётся адресом и сертификатом. Здесь добавляется второй кластер devcluster.
kubectl config --kubeconfig=~/.kube/config set-cluster devcluster --server=https://192.168.1.10 --certificate-authority=devclusterca.crt
# Пространство имён - то, что будет использоваться в этом контексте. Если оно не существует, его нужно создать
kubectl create ns
# Пользователь задаётся его сертификатом X.509 (о других опциях позже) 
kubectl config --kubeconfig=~/.kube/config set-credentials vasya --client-certificate=vasya.crt --client-key=vasya.key
# После настройки всех параметров можно создавать контекст
kubectl set-context devcluster --cluster=devcluster --namespace=devspace --user=vasya

Deployment

Это стандартный способ развёртывания контейнеров масшабируемым способом. Масштабирование осуществляет механизм ReplicaSet.
Деплой предоставляет возможность RollingUpdate для обновлений без простоя.

# Императивный способ создания деплоя
kubectl create deploy
# Справка
kubectl create deploy -h |less
 
kubectl create deploy firstnginx --image=nginx --replicas=3
deployment.apps/firstnginx created
 
kubectl get all
NAME                             READY   STATUS              RESTARTS   AGE
pod/firstnginx-d8679d567-4cxpj   1/1     Running             0          11s
pod/firstnginx-d8679d567-9sl2r   1/1     Running             0          11s
pod/firstnginx-d8679d567-vwkh7   0/1     ContainerCreating   0          11s
 
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   17h
 
NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/firstnginx   2/3     3            2           11s
 
NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/firstnginx-d8679d567   3         3         2       11s

DaemonSet

Запускает по одному экземпляру приложения на каждой ноде. Обычно используется для всяких агентов мониторинга, kube-proxy.
Если нужно запустить демонсет на управляющих нодах, то надо конфигурировать toleration в нём для преодоления taint на мастер-нодах.

# В пользовательском неймспейсе нет демонсетов. Смотрим во всех неймспейсах:
kubectl get ds -A
NAMESPACE     NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
kube-system   calico-node   3         3         3       3            3           kubernetes.io/os=linux   18h
kube-system   kube-proxy    3         3         3       3            3           kubernetes.io/os=linux   18h
 
# Посмотреть, как настроен демонсет calico
kubectl get ds -n kube-system calico-node -o yaml |less

Там описаны tolerations

      tolerations:
      - effect: NoSchedule
        operator: Exists
      - key: CriticalAddonsOnly
        operator: Exists
      - effect: NoExecute
        operator: Exists

https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

# Создать болванку для демонсета (create deploy, да)
kubectl create deploy mydaemon --image=nginx --dry-run=client -o yaml > mydeamon.yaml

В получившемся файле mydeamon.yaml нужно

  1. kind исправить на DaemonSet
  2. удалить строку replicas: 1
  3. удалить строку strategy: {}
kubectl apply -f mydeamon.yaml
 
kubectl get ds
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
mydaemon   2         2         2       2            2           <none>          27s
 
kubectl get po -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP                NODE     NOMINATED NODE   READINESS GATES
firstnginx-d8679d567-4cxpj   1/1     Running   0          51m   192.168.185.129   t-k8s3   <none>           <none>
firstnginx-d8679d567-9sl2r   1/1     Running   0          51m   192.168.100.193   t-k8s2   <none>           <none>
firstnginx-d8679d567-vwkh7   1/1     Running   0          51m   192.168.185.130   t-k8s3   <none>           <none>
mydaemon-cw7x8               1/1     Running   0          75s   192.168.185.131   t-k8s3   <none>           <none>
mydaemon-srrb8               1/1     Running   0          75s   192.168.100.194   t-k8s2   <none>           <none>

StatefulSet

Подвид деплоя, предназначенный для stateful-приложений или для тех. кому нужно хотя бы одно из перечисленного:

  • Стабильные и уникальные сетевые имена
  • Стабильное постоянное хранилище данных
  • Упорядоченное развёртывание и масштабирование
  • Упорядоченное и автоматическое «плавающее обновление» (rolling updates)
Deployment StatefulSet
Для Stateless-приложений Для Stateful-приложений
Все поды создаются параллельно Поды создаются последовательно один за другим (pod-0..pod-X)
Поды удаляются в случайном порядке Поды удаляются в обратном порядке (pod-X..pod-0)
Поды имеют случайные имена Поды имеют чётко определённые имена <name>-<index>
Все поды используют один и тот же PV (persistent volume) Каждая реплика использует свой PV. При перезапуске реплики она получает то же имя и подхватывает тот же PV.
Легко масштабировать Трудно масштабировать

Перед использованием стейтфул-сетов нужно настроить общее хранилище. Если удалить ст-сет, то их постоянные тома не удалятся.
Чтобы обеспечить доступ к подам ст-сета, используется безголовый сервис (headless service). В этом случае во внутреннем DNS кубера будет не имя сервиса, а имена всех подов за ним, что позволит обращаться по имени напрямую к подам.
То, что поды будут остановлены после удаления ст-сета, не гарантировано, поэтому рекомендуется сначала масштабировать реплики в ст-сете до нуля.

Ст-сет похож на деплой, только сервис у него безголовый (clusterIP: None) и присутствует volumeClaimTemplates, определяющий PVC для подов в ст-сете.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  # Headless service
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 1Gi

Запуск одиночных подов

Это нужно только для тестирования, поиска проблем или анализа, потому что

  1. Нет автоперезапуска
  2. Нет балансировки
  3. Нет защиты от простоя при обновлении

Так что в обычной ситуации нужно использовать деплой, д-сет и ст-сет.

# Справка
kubectl run -h |less
# Запустить под, который закончит работу через 1 ч.
kubectl run sleepy --image=busybox -- sleep 3600

Init container

Init container подготавливает почву для запуска основного контейнера в поде. Основной запускается после того, как отработал инициатор.
https://kubernetes.io/docs/concepts/workloads/pods/init-containers/

Простейший вариант

initme.yaml
apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  # These containers are run during pod initialization
  initContainers:
  - name: install
    image: busybox
    command:
    - sleep
    - "30"

https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-initialization/#create-a-pod-that-has-an-init-container

kubectl apply -f initme.yaml
 
kubectl get po
NAME                         READY   STATUS     RESTARTS   AGE
firstnginx-d8679d567-4cxpj   1/1     Running    0          3h35m
firstnginx-d8679d567-9sl2r   1/1     Running    0          3h35m
firstnginx-d8679d567-vwkh7   1/1     Running    0          3h35m
init-demo                    0/1     Init:0/1   0          25s
mydaemon-cw7x8               1/1     Running    0          164m
mydaemon-srrb8               1/1     Running    0          164m
 
kubectl get po
...
init-demo                    0/1     PodInitializing   0          36s
 
kubectl get po
...
init-demo                    1/1     Running   0          46s

Масштабирование

Деплой, д-сет и ст-сет могут быть масштабированы командой kubectl scale.
Есть ещё HorizontalPodAutoscaler, который автоматически увеличивает кол-во реплик при достижении некоего порога нагрузки (в рамках CKA это не рассматривается).

kubectl scale deploy firstnginx --replicas 5
deployment.apps/firstnginx scaled
 
kubectl get all --selector app=firstnginx
NAME                             READY   STATUS    RESTARTS   AGE
pod/firstnginx-d8679d567-4cxpj   1/1     Running   0          3h40m
pod/firstnginx-d8679d567-9sl2r   1/1     Running   0          3h40m
pod/firstnginx-d8679d567-rmdqp   1/1     Running   0          50s
pod/firstnginx-d8679d567-vwkh7   1/1     Running   0          3h40m
pod/firstnginx-d8679d567-xvtm4   1/1     Running   0          50s
 
NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/firstnginx   5/5     5            5           3h40m
 
NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/firstnginx-d8679d567   5         5         5       3h40m

Sidecar container

Обычно под содержит один контейнер, но иногда бывают случаи, когда в поде нужен дополнительный контейнер для представления или модификации данных основного контейнера. Виды таких контейнеров-спутников:

  • Sidecar: доп. функционал к основному контейнеру
  • Ambassador: прокси для доступа извне
  • Adapter: обрабатывает вывод основного контейнера

Тома, подключенные к поду, используются как общее хранилище. Основной контейнер пишет туда, а сайдкар читает это.
Том может быть подключен как через PVC, так и непосредственно.

sidecarlog.yaml
apiVersion: v1
kind: Pod
metadata:
  name: two-containers
spec:

  volumes:
  - name: shared-data
    emptyDir: {}

  containers:

  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html

  - name: busybox-container
    image: busybox
    volumeMounts:
    - name: shared-data
      mountPath: /messages
    command: ["/bin/sh"]
    args: ["-c", "echo hello from the cluster > /messages/index.html && sleep 600" ]
kubectl apply -f sidecarlog.yaml
pod/two-containers created
 
# Запустить cat в сайдкар-контейнере, видно результат из основного
kubectl exec -it two-containers -c nginx-container -- cat /usr/share/nginx/html/index.html
hello from the cluster

Pod volumes

Pod volumes - это часть спецификации пода, где прямо в манифесте пода жёстко заданы настройки хранилища. Это нормально, но негибко. Так могут быть заданы любые типы хранилища. Также, для монтирования pod volumes может использоваться ConfigMap.

Типы хранилищ для подключения

kubectl explain pod.spec.volumes |less

Пример самого простого общего хранилища - emptyDir. Это временная общая папка, существующая только когда существует под.

morevolumes.yaml
apiVersion: v1
kind: Pod
metadata:
  name: morevol
spec:
  containers:
  - name: centos1
    image: centos:7
    command:
      - sleep
      - "3600"
    volumeMounts:
      - mountPath: /centos1
        name: test
  - name: centos2
    image: centos:7
    command:
      - sleep
      - "3600"
    volumeMounts:
      - mountPath: /centos2
        name: test
  volumes:
    - name: test
      emptyDir: {}

Если запустить этот манифест и создать файл в /centos1 первого контейнера, то во втором контейнере в /centos2 этот файл будет виден.

kubectl exec -it morevol -c centos1 -- touch /centos1/centos1file
kubectl exec -it morevol -c centos2 -- ls /centos2
centos1file

Persistent volume (PV)

Могут быть созданы вручную или автоматически (Storage class + Storage provisioner). Поды не могут подключать PV непосредственно, для подключения нужна заявка (PVC, persistent volume claim).

PV объёмом 2 ГБ, локальное (что в кластере бессмысленно) по каталоге /mydata, режим доступа ReadWriteOnce.

pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv-volume
  labels:
      type: local
spec:
  storageClassName: demo
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mydata"

storageClassName используется как ярлык, без него PVC не сможет подключиться к PV.

# Проверка
kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-volume   2Gi        RWO            Retain           Available           demo                    38s

Persistent volume claim (PVC)

Позволяет поду подключиться к PV, некая заявка на предоставление места на диске. Указывается в манифесте пода.

Если не будет указан storageClassName, то PVC будет в состоянии подключиться к PV и будет в состоянии pending.

pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pv-claim
spec:
  storageClassName: demo
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
# Проверка
kubectl get pv,pvc
NAME                         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/pv-volume   2Gi        RWO            Retain           Bound    default/pv-claim   demo                    14m
 
NAME                             STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pv-claim   Bound    pv-volume   2Gi        RWO            demo           8m28s

Видно, что размер PVC 2 ГБ, несмотря на то, что требовался 1 ГБ. Но нельзя запросить половину объёма от PV, поэтому размер 2 ГБ.

Подключение PV к поду

В spec.volumes задан pv-storage, который использует pv-claim и монтируется в /usr/share/nginx/html. В манифесте пода не указано ничего, что касается каких-либо параметров хранилища, что совершенно отделяет его настройки от настроек хранилища (decoupling).

pv-pod.yaml
kind: Pod
apiVersion: v1
metadata:
   name: pv-pod
spec:
  volumes:
    - name: pv-storage
      persistentVolumeClaim:
        claimName: pv-claim
  containers:
    - name: pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: pv-storage
# После запуска пода пишем на PV
kubectl exec -it pv-pod -- touch /usr/share/nginx/html/hello.html

Так как PV смотрит на локальную ФС, теперь надо понять, куда это дело записалось.

# Выяснить путь (path), здесь: /mydata
kubectl describe pv
# Где запустился под
kubectl get po pv-pod -o wide
NAME     READY   STATUS    RESTARTS   AGE     IP                NODE     NOMINATED NODE   READINESS GATES
pv-pod   1/1     Running   0          5m11s   192.168.185.134   t-k8s3   <none>           <none>

Под запустился на t-k8s3, так что там в каталоге /mydata должен теперь лежать hello.html.
Так как PV локальный, то на других нодах hello.html не будет, равно как и каталога /mydata.

В целом путь такой: PV → PVC (cсылается на имя PV) → Pod volume (cсслыется на имя PVС) → Pod volumeMount (cсслыется на имя volume).

StorageClass

Позволяет автоматически предоставлять хранилище в пользование. Даже если storageClass не используется в таком виде, он служит для связи PV и PVC как селектор.
В кластере может существовать несколько объектов storageClass для разных типов хранилищ (быстрое/медленное и т. п.). Один storageClass может быть задан по умолчанию

kubectl patch storageclass my-sc -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Чтобы предоставлять хранилище в пользование, для storageClass необходим Storage Provisioner.
Storage Provisioner нужно настроить до storageClass.

В PV и PVC свойство storageClass может быть задано для подключения к определённому хранилищу, если в кластере доступно несколько. Если оно не задано, подключение будет идти к storageClass по умолчанию. Если storageClass по умолчанию не задан, PVC будет висеть в статусе Pending.

Storage provisioners (providers)

Storage Provisioner (provider) работает с storageClass, чтобы автоматически предоставлять место на хранилище. Он запускается как под в кластере, предоставляющий функции контроля доступа. Когда провайдер настроен, PV больше не нужно настраивать руками.
Чтобы провайдер работал, ему нужен доступ к API, ведь он создаёт ресурсы, и ему нужны разрешения на это.
Roles и RoleBindings используются для создания таких разрешений. ServiceAccount создаётся для подключения пода к нужной RoleBinding.

На мастер-ноде

sudo apt install nfs-server -y
sudo mkdir /nfsexport
sudo echo "/nfsexport *(rw,no_root_squash)" > /etc/exports
sudo systemctl restart nfs-server

На остальных нодах

sudo apt install nfs-client -y
# Проверить, видят ли они NFS-шару
showmount -e <control node IP>

На мастер-ноде

# Установить helm: скачать релиз и скопировать бинарник в /usr/local/bin
tar xvf helm.tar.gz
sudo cp linux-amd64/helm /usr/local/bin
 
# Добавить репозиторий
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
# Установить
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=192.168.1.21 --set nfs.path=/nfsexport

Провайдер появляется в списке подов.

Дальше создаётся PVC. storageClassName должен соответствовать имени storageClass (список - kubectl get storageclass)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-test
spec:
  storageClassName: nfs-client # SAME NAME AS THE STORAGECLASS
  accessModes:
    - ReadWriteMany #  must be the same as PersistentVolume
  resources:
    requests:
      storage: 50Mi

После применения создаётся PVC с соответствующим storageClassName.

Чтобы постоянно не указывать в манифесте PVC storageClassName, нужно сделать какой-то storageClass по умолчанию. Если storageClass по умолчанию не указан и применён манифест PVC, где нет storageClassName, то kubectl get pvc покажет, что PVC находится в статусе pending, и если посмотреть kubectl describe pvc <pending pvc name>, то там будет написано:
No persistent volumes available for this claim and no storage class is set

Применяем патч, указывающий, что этот storageClass нужно сделать по умолчанию:

kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/

После применения патча PVC без storageClassName будут успешно привязаны.

kubectl get pvc
NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
another-nfs-pvc-test   Bound    pvc-68404b95-b113-4f49-bd19-75b959f651d6   50Mi       RWX            nfs-client     14m

Configmaps/secrets as volumes

Configmap и secret по сути одно и то же, только секрет зашифрован в base64. Configmap используется для хранения переменных окружения, параметров запуска или файлов конфигурации. Когда конфиг хранится внутри configmap или secret, он монтируется как том внутри контейнера. Есть ограничение по объёму - 1 МБ. Если нужно больше, то придётся использовать реальные PV.

echo Hello world > index.html
kubectl create cm webindex --from-file=index.html
kubectl describe cm webindex # видно, что есть ключ index.html и значение Hello world.
kubectl create deployment webserver --image=nginx
kubectl edit deploy webserver

spec.template.spec - добавить раздел volumes: и внутри containers: сделать volumeMounts:

    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cmvol
      volumes:
      - name: cmvol
        configMap:
          name: webindex

Именование важно: имя volumeMount ссылается на имя тома, а тот, в свою очередь, на имя configMap. Проверяем:

kubectl exec webserver-76d44586d-l7wzz -- cat /usr/share/nginx/html/index.html
Hello world

Networking

В k8s сетевое взаимодействие работает на несольких уровнях:

  • Между контейнерами - IPC (межпроцессное взаимодействие), это как внутри одного компьютера процессы взаимодействуют друг с другом.
  • Между подами - через сетевой плагин.
  • Между подами и сервисами - через ресурсы сервиса.
  • Между внешними пользователями и сервисами - с помощью сервисов и Ingress.

Ноды подключены к внешней сети и также имеют виртуальную кластерную сеть. В кластерной сети работают сервисы, которые балансируют нагрузку на входящие в них поды. У подов своя сеть. Для внешнего пользователя получить доступ к кластеру можно двумя способами:

  1. Обращение через DNS на внешний балансировщик, который пробрасывает запросы на определённый порт какой-либо ноды кластера. Сервис в этом случае должен быть настроен в режиме NodePort (публикуется определённый порт на всех нодах кластера во внешней сети).
  2. Обращение на Ingress. Это ресурс кластера для внешних подключений, который пробрасывает запросы на сервисы. Сервис может быть настроен и как NodePort, и как ClusterIP (в этом случае сервис только имеет адрес в виртуальной кластерной сети). Ingress может работать только с трафиком HTTP(S). Ingress не только является балансировщиком, но и интегрирован в Kubernetes API, что облегчает его настройку.

Сетевой плагин нужен для обеспечения связи между подами. Его необходимо ставить отдельно, т. к. он не входит в стандартный k8s. Существует несколько реализаций сетевого плагина, разные плагины поддерживают разные набор функций. Часто используется Calico, т. к. у него есть NetworkPolicy.

Services

Сервисы обеспечивают доступ к подам. Если за сервисом несколько подов то сервис будет балансировать трафик между подами. Есть несколько типов сервисов:

  • ClusterIP - сервис доступен только внутри кластерной виртуальной сети
  • NodePort - сервис доступен на определённом порту всех кластерных нод
  • LoadBalancer - этот вариант доступен у облачных провайдеров. Обеспечивает доступ к ClusterIP- и NodePort-сервисам.
  • ExternalName - сервис привязывается ко внешнему DNS-псевдониму (DNS CNAME record).

Команда kubectl expose публикует приложения через их поды, реплики или деплой. Через деплой лучше всего.
Команда kubectl create service делает то же самое, но, вероятно, менее удобно.

kubectl create deploy webshop --image=nginx --replicas=3
kubectl expose deploy webshop --type=NodePort --port=80
kubectl describe svc webshop
...
Selector:	app=webshop

С помощью селектора сервис подключается к одноимённым ярлыкам подов (Labels: app=workshop). Теперь приложение доступно по адресу любой ноды (в т. ч. управляющей) + порт (ip:nodePort).

Ingress controller

Ингресс предоставляет внешний доступ к сервисам кластера. Работает с внешним DNS, чтобы доступ был по URL. Состоит из 2 частей: балансировщик, доступный из внешней сети и API-ресурс, контактирующий с сервисами, чтобы обнаружить поды за ними. Ingress надо ставить отдельно, его нет в стандартной установке Кубера. Ингресс работает только с HTTP/HTTPS. Использует селектор в сервисах, чтобы подключаться к подам.

Трафик управляется согласно правилам, описанным в ингресс-ресурсе. Ингресс может быть настроен для следующих функций:

  1. Сделать сервисы доступными по внешним URL
  2. Балансировка трафика
  3. Терминирование SSL/TLS
  4. Offer name based virtual hosting
helm upgrade --install ingress-nginx ingress-nginx --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace
kubectl get pods -n ingress-nginx
kubectl create deploy nginxsvc --image=nginx --port=80
kubectl expose deploy nginxsvc
 
# Создать правило ингресса:
kubectl create ingress nginxsvc --class=nginx --rule=nginxsvc.ru/*=nginxsvc:80

Если посмотреть внутрь созданного ингресса (kubectl describe ingress nginxsvc), то там не будет селектора - ингресс работает через правило.
Селектор играет роль в сервисе, поэтому если выдаются 500-е ошибки, надо проверять селектор сервиса.

Узнать порт доступа к ингрессу извне:

kubectl get service -n ingress-nginx ingress-nginx-controller
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.109.33.100   <pending>     80:32452/TCP,443:30462/TCP   12m

Теперь после заведения DNS-записи можно обращаться к http://nginxsvc.ru:32452 или https://nginxsvc.ru:30462

Configuring Ingress

# Справка с примерами
kubectl create ingress -h |less
# Можно задать несколько правил для одного хоста
kubectl create ingress mygress --rule="/mygress=mygress:80" --rule="/yourgress=yourgress:80"
# Точно так же можно задать несколько виртуальных хостов
kubectl create ingress nginxsvc --class=nginx --rule=nginxsvc.info/*=nginxsvc:80 --rule=otherserver.org/*=otherserver:80

IngressClass

В одном кластере могут находиться разные ингресс-контроллеры каждый со своей конфигурацией. Контроллер задаётся в IngressClass. При создании ингресс-правил опция --class должна быть использована для задания роли на определённом ингресс-контроллере. Если --class не используется, тогда должен быть задан класс по умолчанию: это ingressclass.kubernetes.io/is-default-class: "true" в аннотациях.

# После создания ингресс-контроллера создаётся API-ресурс IngressClass, содержимое которого можно посмотреть командой
kubectl get ingressclass -o yaml
 
# Отредактировать уже существующий ингресс-класс nginx, сделав его по умолчанию:
kubectl edit ingressclass nginx
apiVersion: v1
items:
- apiVersion: networking.k8s.io/v1
  kind: IngressClass
  metadata:
    annotations:
      ingressclass.kubernetes.io/is-default-class: "true"
      meta.helm.sh/release-name: ingress-nginx
      meta.helm.sh/release-namespace: ingress-nginx
...

Port forwarding

Нужен для тестирования доступа к приложению без возни с сервисами и ингрессом.

# Редирект с 1234 на 80
kubectl port-forward mypod 1234:80

По умолчанию запускается на переднем плане, поэтому чтобы освободить консоль, можно нажать Ctrl+Z или сразу запускать команду с & в конце. Например

kubectl port-forward mypod 1234:80 &
curl localhost:1234

Lab 5

kubectl create deploy apples --image=nginx --replicas=3
kubectl expose deploy apples --port=80
kubectl create ingress apples --class=nginx --rule=my.fruit/*=apples:80
kubectl get svc -n ingress-nginx # выяснить порт
echo "127.0.0.1  my.fruit" |sudo tee --append /etc/hosts
curl my.fruit:31284

Cluster nodes

# Информация по кублету
systemctl status kubelet
# Также см. /var/log или journalctl
# Основная информация о ноде
kubectl describe node $(hostname)
# Если установлен сервер метрик, то получить информацию о загрузке процессора/памяти можно
kubectl top nodes

crictl - утилита для получения информации о контейнерах (вместо docker/podman). Чтобы использовать её, нужно задать runtime-endpoint и image-endpoint. Самый удобный способ - на нодах, где планируется использование crictl, отредактировать файл /etc/crictl.yaml (см. пример в репе cka/crictl.yaml).

Всё это на конкретной ноде. Без sudo не работает, прав не хватит.

# Выдать список контейнеров
sudo crictl ps
# Выдать список подов
sudo crictl pods
# В списке контейнеров имена неуникальные. Если что, то надо смотреть детальную информацию по контейнеру по его ID
sudo crictl inspect 82e883gf8v8re
# Список образов
sudo crictl images
# Скачать образ mysql с docker.io
sudo crictl pull docker.io/library/mysql

Crictl - это только средство для контейнеров, оно не имеет полного функционала docker/podman.

Static pods

Это поды, запущенные непосредственно процессом kubelet на ноде. Таким образом работают основные сервисы Кубера. Админ может добавить такой под, скопировав манифест в каталог /etc/kubernetes/manifests. Этот каталог можно поменять на другой, указав его в параметре staticPodPath файла /var/lib/kubelet/config.yaml и перезапустив кублет.

sudo systemctl restart kubelet

Никогда не нужно делать это на управляющей/контрольной ноде, т. к. поды Кубера перестанут работать! На рабочих нодах статических подов нет, поэтому запускать дополнительные статические поды нужно только на них.

# На контрольной ноде генерим манифест пода
kubectl run staticpod --image=nginx --dry-run=client -o yaml > staticpod.yaml
# Вывести содержимое и затем скопировать в буфер
cat staticpod.yaml
# На РАБОЧЕЙ ноде, открыть новый файл и вставить туда содержимое
sudo nano /etc/kubernetes/manifests/staticpod.yaml
# На контрольной ноде, если вывести список подов, то новый статический под будет показываться c именем ноды-хозяина в конце имени.
kubectl get po
NAME
staticpod-worker1
...

Managing node state

kubectl cordon # больше не принимать нагрузку
kubectl drain # больше не принимать нагрузку и убрать все поды
	--ignore-daemonsets # и демонсеты тоже
	--delete-emptydir-data # удалить данные из томов emptyDir
kubectl uncordon # вернуть ноду в список доступных для распределения нагрузки

cordon/drain ставят taint на ноду, что и даёт эффект того, что на неё нельзя распределить нагрузку.

Поды автоматически после возвращения ноды не распределяются.

Managing node services

На рабочей ноде это кублет и контейнерный движок, управляемый systemd.

# Состояние кублета\старт\стоп
sudo systemctl status\start\stop kubelet
# См. процесс кублета
ps aux |grep kubelet
# Процессы контейнерного движка
ps aux |grep containerd

Если убить (kill) процесс kubelet, то он опять запустится, т. к. в юните кублета задано его перезапускать.

systemctl cat kubelet.service
...
[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10
...

Если просто остановить сервис (sudo systemctl stop kubelet), то он сам перезапускаться не будет.

Metrics server

Сервер метрик нужен для мониторинга нод и состояния подов. Его нужно установить отдельно.
https://github.com/kubernetes-sigs/metrics-server

# Установка (https://github.com/kubernetes-sigs/metrics-server?tab=readme-ov-file#installation)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Вывести поды системного пространства имён, там под сервера метрик не может запуститься
kubectl -n kube-system get po
NAME                                       READY   STATUS    		RESTARTS     AG
metrics-server-6db4d75b97-5lslb            0/1     ContainerCreating    0            2m16s
 
# Проверяем логи
kubectl logs -n kube-system metrics-server-6db4d75b97-5lslb
# Везде ругань на сертификат.
 
# Редактируем деплой
kubectl -n kube-system edit deploy metrics-server

Там в spec.template добавить –kubelet-insecure-tls

    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=10250
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-insecure-tls
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
# После этого сервер метрик начинает работать и можно проверить ресурсы
kubectl top pods
NAME                                               CPU(cores)   MEMORY(bytes)
nfs-subdir-external-provisioner-59cb575ccc-gcxr4   1m           7Mi
nginxsvc-5f8b7d4f4d-fwnnj                          0m           3Mi
webserver-76d44586d-l7wzz                          0m           7Mi

ETCD backup

Etcd работает как статический под на мастер-ноде, его потеря значит потеря всей конфигурации кластера. Это делается под рутом, используя etcdctl, который нужно сначала установить.

sudo apt install etcd-client
# Справка (старая версия API)
sudo etcdctl -h
# Если запустить c ETCDCTL_API=3 (который и нужен), то справка будет другой
sudo ETCDCTL_API=3 etcdctl -h
# Процессы apiserver и etcd, подробности (путь к сертификатам и ключу, это нужно в дальнейшем)
ps aus |grep etcd
# Проверка работоспособности etcdctl, выводит ключи
sudo ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key get / --prefix --keys-only
# Бэкап
sudo ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key snapshot save /tmp/etcdbackup.db
# Статус резервной копии (выведет табличку со сводкой по резервной копии)
sudo ETCDCTL_API=3 etcdctl --write-out=table snapshot status /tmp/etcdbackup.db

Очень желательно бэкап держать в нескольких местах для надёжности.

ETCD restore

# Можно вытереть пару деплоев, чтобы был виден результат восстановления
kubectl delete deploy apples
kubectl delete deploy webshop
# Переместить манифесты статических подов - это остановит их (через какое-то время).
cd /etc/kubernete/manifests
sudo mv * ..
# Проверить отсутствие пода etcd
sudo crictl ps 
# Восстановить базу в другой каталог (он создастся автоматически)
sudo ETCDCTL_API=3 etcdctl snapshot restore /tmp/etcdbackup.db --data-dir /var/lib/etcd-restored
# Отредактировать конфиг, где в spec.volumes.hostPath.path /var/lib/etcd указать новый каталог с базой (/var/lib/etcd-restored)
sudo nano /etc/kubernetes/etcd.yaml
# Вернуть манифесты на место
sudo mv ../*yaml .
# Проверить наличие пода etcd
sudo crictl ps
# Проверить, что удалённые ранее деплои опять на месте
kubectl get deploy

Cluster node upgrades

Пропускать минорные версии нельзя (1.23 нельзя обновить на 1.25, только на 1.24).
Порядок обновления: kubeadm → control plane → workers.
https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/

Если репозитории старые (apt.kubernetes.io или yum.kubernetes.io), то их нужно обновить (https://kubernetes.io/blog/2023/08/15/pkgs-k8s-io-introduction/#how-to-migrate).

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo apt-get update
# Find the latest patch release for Kubernetes 1.29 using the OS package manager:
sudo apt-cache madison kubeadm
   kubeadm | 1.29.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.29/deb  Packages
   kubeadm | 1.29.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.29/deb  Packages
   kubeadm | 1.29.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.29/deb  Packages
 
# replace x in 1.29.x-* with the latest patch version
sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.29.2-*' && \
sudo apt-mark hold kubeadm
 
# Verify that the download works and has the expected version:
kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"29", GitVersion:"v1.29.2", GitCommit:"4b8e819355d791d96b7e9d9efe4cbafae2311c88", GitTreeState:"clean", BuildDate:"2024-02-14T10:39:04Z", GoVersion:"go1.21.7", Compiler:"gc", Platform:"linux/amd64"}
 
# Verify the upgrade plan:
sudo kubeadm upgrade plan

https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/#considerations-when-upgrading-etcd

# trigger a graceful kube-apiserver shutdown
sudo killall -s SIGTERM kube-apiserver
# wait a little bit to permit completing in-flight requests
sleep 20
# execute a kubeadm upgrade command
sudo kubeadm upgrade apply v1.29.2
 
# draining
kubectl drain $(hostname) --ignore-daemonsets
# Upgrade the kubelet and kubectl:
sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.29.2-*' kubectl='1.29.2-*' && \
sudo apt-mark hold kubelet kubectl
# Restart the kubelet:
sudo systemctl daemon-reload
sudo systemctl restart kubelet
# Uncordon the node
kubectl uncordon $(hostname)

Upgrade worker nodes

sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.29.2-*' && \
sudo apt-mark hold kubeadm
# upgrade the local kubelet configuration
sudo kubeadm upgrade node
# На мастер-ноде:
kubectl drain k3 --ignore-daemonsets --delete-emptydir-data
# На рабочей ноде Upgrade the kubelet and kubectl:
sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.29.2-*' kubectl='1.29.2-*' && \
sudo apt-mark hold kubelet kubectl
# Restart the kubelet:
sudo systemctl daemon-reload
sudo systemctl restart kubelet
# На мастер-ноде: Uncordon the node
kubectl uncordon k3

Highly available cluster

2 варианта:

  1. Stacked control plane nodes - когда control plane и etcd работают на одной ноде. Оптимально иметь 3 и более узлов.
  2. External etcd cluster - etcd в отдельном кластере. Нужно больше узлов.

Для отказоустойчивого кластера нужен балансировщик, например, keepalived.

Схема примерно такая: на каждой мастер-ноде стоит

  • API-сервер:6443
  • haproxy:8443, обеспечивающий доступ к API
  • keepalived c VIP:8443, который балансирует запросы к мастер-нодам. Клиент kubectl обращается к VIP keepalived.
# kubeadm во время развёртывания кластера нужно настраивать на VIP:8443.
sudo kubeadm init --control-plane-endpoint "192.168.1.100:8443" --upload-certs
# Установить сетевой плагин
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
# Подключить остальные мастер-ноды (с ключом --control-plane)

На всех нодах скопировать /etc/kubernetes/admin.conf в профиль. Подключить рабочие ноды.

Для клиента /etc/kubernetes/admin.conf с любой мастер-ноды нужно поместить в каталог ~/.kube, затем установить kubectl.

Scheduler

Планировщик отвечает за размещение подов на рабочих узлах. Узлы отбираются по критериям, которые можно задать:

  • Требования к ресурсам
  • Affinity/Anti-affinity (более сложные, чем ярлыки, правила размещения подов)
  • Taints/Tolerations и т. д.

Планировщик перед размещением находит узлы-кандидаты и оценивает их, затем подбирает узел с наивысшей оценкой. Дальше планировщик начинает привязку, уведомляет API-сервер и передаёт задачу кублету, который уже инструктирует контейнерный движок (CRI) скачать образ и запустить контейнер.

8.2 Node selector

nodeSelector - поле в pod.spec, которое указывает на соответствующий ярлык на узле и запускает под там.

# Задать ярлык на ноде
kubectl label nodes worker1 disktype=ssd
# Удалить ярлык
kubectl label nodes worker1 disktype-

Пример манифеста пода

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

В pod.spec есть ещё и nodeName, где можно задать конкретное имя узла, на котором всегда будет запускаться этот под. Но так не рекомендуется делать - если узел выйдет из строя, то под не запустится больше нигде.

8.3 Affinity

Более продвинутые правила размещения подов.

  • Node affinity - правило на узле, которое размещает там поды с совпадающими ярлыками.
  • Inter-pod affinity - правило, по которому одни поды размещаются на том же узле, что и другие.
  • Anti-affinity - применимо только к подам, не к узлам.

Т. е.,

  1. Под с node-affinity-ярлыком key=value будет размещён только на узлах, имеющих совпадающий ярлык.
  2. Под с pod-affinity-ярлыком key=value будет размещён только на узлах, имеющих поды с совпадающим ярлыком.

Есть 2 режима работы affinity:

  1. requiredDuringSchedulingIgnoredDuringExecution - требование соответствия ограничению
  2. preferredDuringSchedulingIgnoredDuringExecution - предпочтение соответствия ограничению (если не может быть удовлетворено - игнорируется)

Affinity применяется только к планируемым подам, не к работающим. Те, которые уже работают, меняться не будут.

Affinity - это не просто соответствие ярлыков, а ещё и логика.
Здесь выбираются ноды, где тип равен blue или green.

spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: type
            operator: In
            values:
            - blue
            - green

Здесь - ноды, где задан ключ storage.

        nodeSelectorTerms:
        - matchExpressions:
          - key: storage
            operator: Exists

При использовании pod affinity/anti-affinity свойство topologyKey обязательно к указанию.

spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone

topologyKey ссылается на ярлык, который прописан на ноде. Обычно он имеет наклонную черту, например, kubernetes.io/host.
topologyKey разрешает запускать поды только на узлах, имеющих этот ключ. Это позволяет создавать сегментировать кластер на зоны, где запускается нагрузка. Если topologyKey не найден на ноде, то он игнорируется в настройках pod affinity.

redis-with-pod-affinity.yaml - интересный пример. В манифесте написано не запускать под, если узел уже имеет под с ярлыком app=store, вместе с тем, под сам имеет этот ярлык. Это приведёт к тому, что на одной ноде будет запущен только один под, т. к. второй уже не уживётся с таким же экземпляром на одном узле.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

web-with-pod-affinity.yaml - то же самое: один web-server не будет работать с другим на одной и той же ноде, но зато будет запускаться только там, где есть под с ярлыком app=store, т. е. с redis-cache.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

8.4 Taints / tolerations

Taint применяется к узлам. Это метка, которая препятствует запуску любых подов, если у них нет соответствующего toleration.
Toleration применяется к подам. Это метка, которая позволяет (но не предписывает!) поду запуститься на узле с соответствующим taint.
Если affinity применяется для того, чтобы привязать поды к конкретным узлам, то taint - чтобы исключить их запуск на узлах.
Taints и tolerations гарантируют, что поды не запускаются на неподходящих узлах, таким образом гарантируя, что на выделенных узлах запускаются только поды для конкретных задач.

Есть 3 типа taints:

  1. NoSchedule: не размещать новые поды
  2. PreferNoSchedule: не размещать новые поды, пока есть альтернатива
  3. NoExecute - не размещать и выгнать уже существующие

Taints задаются разными способами.
На контрольных нодах тейнт задан по умолчанию, чтобы там не размещалась рабочая нагрузка.
Команды kubectl drain и kubectl cordon создают тейнт на целевом узле.
Тейнт может быть установлен автоматически кластером в определённых условиях, например, из-за:

  1. нехватки места на ноде
  2. нехватки памяти
  3. нехватки доступных ID процессов
  4. невозможность для планирования
  5. недоступность по сети

И эти автоматические тейнты могут быть преодолены соответствующими толерами, но этого крайне не рекомендуется делать, т.к. когда эти тейнты ставятся, значит, с узлом проблемы.

# Тейнт может быть задан админом:
kubectl taint nodes worker1 key1=value1:NoSchedule
# Удалить тейнт:
kubectl taint nodes worker1 key1=value1:NoSchedule-

Толер необходим для запуска подов на узле с тейнтом, например, так работают поды Кубера на мастер-ноде. Ключ и значение у тейнтов и толеров позволяют задавать тонкие настройки запуска. Например,

kubectl taint nodes worker1 storage=ssd:NoSchedule

позволит запускаться на узле worker1 только тем подам, у которых есть толер storage=ssd. Если у пода есть толер storage=hdd, то он тут не запустится.

Толер в манифесте пода задаётся ключом, оператором и значением:

spec:
  tolerations:
  - key: "storage"
    operator: "Equal"
    value: "ssd"

Equal - оператор по умолчанию. Также часто используется "Exists", в этом случае value игнорируется.

8.6 Limitrange / quota

LimitRange - API-объект, ограничивающий использование ресурсов подом или контейнером в пространстве имён. 3 опции:

  1. type: к чему применяется - к подам или контейнерам
  2. defaultRequest: сколько ресурсов запросит приложение по умолчанию
  3. default: максимум ресурсов, которые может испольовать приложение

Quota - API-объект, ограничивающий количество доступных ресурсов в пространстве имён. Если пространство имён настроено с квотой, приложения в этом неймспейсе обязаны быть с настройками ресурсов в pod.spec.containers.resources.

Т. е., LimitRange задаёт лимиты для каждого приложения, а Quota - для целого пространства имён (всех приложений в нём).

kubectl explain limitrange.spec.limits
kubectl create ns limited
nano limitrange.yaml
kubectl apply -f limitrange.yaml -n limited
kubectl describe ns limited
nano limitedpod.yaml
kubectl run limited --image=nginx -n limited

9.1 CNI & Network plugins

Container Network Interface (CNI) - стандартный сетевой интерфейс, расширяемый плагинами. Собственно сетевое взаимодействие реализуется соответствующим плагином.
Конфиг CNI находится в /etc/cni/net.d, некоторые сетевые плагины держат свою конфигурацию там же.
Остальные плагины имеют стандартные настройки и используют доп. конфигурацию, часто реализуемую через поды.
Доки по CNI: https://github.com/containernetworking/cni

Внутренняя сеть Кубера состоит из 2 частей: calico и API (cluster network).
В случае с calico его ресурсы находятся в системном пространстве имён (работает как DaemonSet)

kubectl get all -n kube-system

IP-диапазон для кластерной сети (см. --service-cluster-ip-range)

ps aux |grep api

9.2 Service auto registration

Кубер запускает поды coredns в системном пространстве имён как внутренние DNS-сервера.
Эти поды опубликованы через сервис kube-dns и любой под в Кубере может обратиться к coredns.
Поды автоматически настраиваются с IP-адресом сервиса kube-dns в качестве своего DNS-сервера.
В результате, поды могут обращаться к сервисам по имени.

Если обращение идёт к сервису в том же пространстве имён, можно обращаться к нему по короткому имени.
Если в другом - то по FQDN (servicename.namespace.svc.clustername).
Имя кластера по умолчанию - cluster.local, записано в Corefile у coredns (хранится как ConfigMap).
Проверить можно

kubectl get cm -n kube-system coredns -o yaml

Тест

kubectl run webserver --image=nginx
kubectl expose pod webserver --port=80
kubectl create ns remote
kubectl run remotebox --image=busybox -n remote -- sleep 3600
kubectl exec remotebox -n remote --nslookup webserver # can't find
kubectl exec remotebox -n remote --nslookup webserver.default.svc.cluster.local # works

9.3 Network policies

Изначально между подами нет сетевых ограничений и они могут свободно общаться даже если находятся в разных пространствах имён. Чтобы регулировать трафик, существуют сетевые политики. Чтобы политики работали, они должны поддерживаться сетевым плагином. Например, плагин weave не поддерживает сетевых политик.
Если политика есть и правило не совпадает, то трафик запрещается.
Если политики нет, трафик разрешается.

В сетевой политике есть 3 разных идентификатора:

  1. Поды (podSelector): разрешает доступ к подам. Само собой, что под не может заблокировать доступ к самому себе.
  2. Пространства имён (namespaceSelector): разрешает доступ к пространствам имён.
  3. Диапазон IP (ipBlock): доступ к блоку IP-адресов.

Когда используется сетевая политика с podSelector или namespaceSelector, то трафик разрешён для подов с совпадающим селектором.
Если есть несколько неконфликтующих сетевых политик, они добавляются друг к другу.

Пример https://github.com/sandervanvugt/cka/blob/master/nwpolicy-complete-example.yaml
Политика access-nginx применяется к подам, у которых есть ярлык app=nginx. Доступ к этим подам будет разрешён только тем подам, у которых есть ярлык access=true. Дальше описаны два пода - nginx и busybox. В этом манифесте у busybox доступа к nginx не будет.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"
...
 
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels: 
    app: nginx
spec:
  containers:
  - name: nwp-nginx
    image: nginx:1.17
...
 
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  labels:
    app: sleepy
spec:
  containers:
  - name: nwp-busybox
    image: busybox
    command:
    - sleep
    - "3600"
# Публикуем под nginx
kubectl expose pod nginx --port=80
# Проверка доступа (будет time out, т. к. доступа нет)
kubectl exec busybox -- wget --spider --timeout=1 nginx
# Прописываем ярлык access=true для busybox, теперь предыдущая команда выполнится успешно
kubectl label pod busybox access=true
 
# См. детали политики
kubectl describe networkpolicy access-nginx
# "Not affecting egress traffic" значит, что исходящий трафик не ограничен.

9.4 Network policies (namespaces)

Network policy - это РАЗРЕШЕНИЕ трафика, но если она есть, то всё остальное запрещается.
Пример: https://github.com/sandervanvugt/cka/blob/master/nwp-lab9-2.yaml
Разрешить любые поды в пространстве имён default.
Следовательно, все остальные доступа не получат.

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: default
  name: deny-from-other-namespaces
spec:
  podSelector:
    matchLabels:
  ingress:
  - from:
    - podSelector: {}

Теперь, если обращаться даже по FQDN из другого пространства имён, ничего не выйдет.

kubectl create ns restricted
kubectl run lab9server --image=nginx -n restricted
kubectl expose po lab9server --port=80 -n restricted
kubectl run sleepybox1 --image=busybox -- sleep 3600
kubectl run sleepybox2 --image=busybox -- sleep 3600
kubectl exec sleepybox1 -- wget --spider --timeout=1 lab9server.restricted.svc.cluster.local # работает

https://github.com/sandervanvugt/cka/blob/master/lesson9lab.yaml

Применить политику: работает в namespace: restricted, можно подключаться из namespace: default с подов с ярлыком access=yes. Важно - namespaceSelector и podSelector в одной связке, не под разными пунктами.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mynp
  namespace: restricted
spec:
  podSelector:
    matchLabels:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: default
      podSelector:
        matchLabels:
          access: "yes"
kubectl exec sleepybox1 -- wget --spider --timeout=1 lab9server.restricted.svc.cluster.local # не работает
kubectl label po sleepybox1 access=yes
kubectl exec sleepybox1 -- wget --spider --timeout=1 lab9server.restricted.svc.cluster.local # работает (что и требуется)
kubectl exec sleepybox2 -- wget --spider --timeout=1 lab9server.restricted.svc.cluster.local # не работает (что и требуется)

10.1 API access

Прямого доступа к ETCD нет, доступ осуществляется через kube-apiserver. Права доступа описываются в Roles и clusterRoles.

Есть 2 способа доступа к API:

  1. kubectl. Это клиентская программа, где настройки описываются стандартно в .kube/config.
    Там описана PKI (public key infrastructure, инфраструктура открытых ключей). Пользователей как таковых нет в Кубере, поэтому подписанные PKI-сертификаты и есть «пользователи».
  2. Приложения. К примеру, некоторые поды берут информацию от API. Чтобы сделать это, используется ServiceAccount. Это особый тип «пользователя», только для приложений (подов).

Итак, kubectl/pod обращаются к API и дальше им нужен доступ к ETCD, для этого надо подключиться к Role или clusterRole для определения полномочий. roleBinding и clusterRoleBinding это привязки к Role и clusterRole для kubectl/pod (типа PVC для PV).

10.2 Security context

Определяет права и настройки доступа для подов и контейнеров. Включает в себя следующее:

  • UID/GID-based discretionary access control (linux permissions)
  • SELinux security labels
  • Linux capabilities (доступ к спецфункциям)
  • AppArmor (эквивалент SELinux)
  • Seccomp
  • AllowPrivilegeEscalation setting (может ли пользователь внутри пода запускать задачи с повышением привилегий)
  • runAsNonRoot (запуск пода под нерутовым пользователем)

Сек. контекст может быть задан на уровне пода или на уровне контейнера. См.

kubectl explain pod.spec.securityContext
kubectl explain pod.spec.containers.securityContext

Набор параметров неодинаковый.
Если одни и те же настройки заданы одновременно и для пода, и для контейнера, приоритет у более специфических настроек (контейнера). А лучше вообще избегать таких ситуаций.
Пример (https://github.com/sandervanvugt/cka/blob/master/security-context.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 2000
  volumes:
  - name: securevol
    emptyDir: {}
  containers:
  - name: sec-demo
    image: busybox
    command: ["sh", "-c", "sleep 3600"]
    volumeMounts:
    - name: securevol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false

10.3 Using ServiceAccounts to Configure API Access

В Кубере нет внутренних пользователей. Они берутся извне:

  • Определяются сертификатами X.509
  • Берутся из внешних источников (Google, AD, etc.)

Сервисные учётки используются для авторизации подов для получения доступа к специфическим ресурсам.

# Сервисные учётки, существующие в текущем контексте
kubectl get sa
# Помимо стандартной, там есть ещё учётка NFS Storage Provisioner, потому что её нужно создавать PVs, а для этого нужен доступ к API.
 
#Все сервис-учётки:
kubectl get sa -A
# Найти запись об используемой сервис-учётке можно в свойствах пода
kubectl describe po <podname> |grep -i serviceaccount

10.4 RBAC

Role-based access control - ролевой доступ. Имеет 3 компонента:

1) Роль: используется в пространстве имён и задаёт права доступа к ресурсам в этом пространстве имён.

kubectl create role -h |less
kubectl get roles
kubectl get roles -A # какие вообще существуют, может пригодиться для изучения
kubectl get role leader-locking-nfs-subdir-external-provisioner -o yaml |less

2) Привязка роли (roleBindings). Роль сама по себе не указывает, кто может её использовать.
roleBindings подключает пользователей или сервис-аккаунты к ролям.

kubectl create rolebinding -h |less
kubectl get rolebinding -A
kubectl get rolebinding ingress-nginx -n ingress-nginx -o yaml |less

RoleRef указывает на роль, к которой идёт привязка, а subjects - кто привязан.

3) Сервисные учётки (ServiceAccounts).
Сервис-учётка авторизует поды, чтобы те могли получить информацию от API.
Все поды имеют сервис-учётку по умолчанию, которая предоставляет им минимальный уровень доступа.
Если этого недостаточно, нужно сделать отдельную сервисную учётку.
У сервис-учётки нет особой конфигурации, они просто используются в привязках, чтобы получить доступ к роли.

Демо
Сделать под alpine (https://github.com/sandervanvugt/cka/blob/master/mypod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: alpine
    image: alpine:3.9
    command:
    - "sleep"
    - "3600"
kubectl apply -f mypod.yaml
# Если посмотреть в свойства пода, то там в качестве сервис-учётки будет default.
kubectl describe po mypod -o yaml
# Если внутри пода попробовать постучаться к API, то будет отлуп
kubectl exec -it mypod --sh
apk add curl
curl https://kubernetes/api/v1 --insecure
# Когда подключение идёт с токеном от сервис-учётки Default, то всё работает
TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
echo $TOKEN # Проверить, прочитался ли токен
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1 --insecure
# Если полезть дальше, то прав у стандартной сервис-учётки не хватит (User "default" cannot list resource "pods" in API group)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods --insecure
# Для этого нужно настроить RBAC.

Создаём сервис-учётку (https://github.com/sandervanvugt/cka/blob/master/mysa.yaml)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: mysa

Создаём роль (https://github.com/sandervanvugt/cka/blob/master/list-pods.yaml)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: list-pods
  namespace: default
rules:
  - apiGroups:
    - ''
    resources:
    - pods
    verbs:
    - list

Или из командной строки:

kubectl create role list-pods --verb=list --resource=pods -n default

Создаём привязку (https://github.com/sandervanvugt/cka/blob/master/list-pods-mysa-binding.yaml):

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: list-pods-mysa-binding
  namespace: default
roleRef:
  kind: Role
  name: list-pods
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: mysa
    namespace: default

В поде надо поменять сервис-учётку (https://github.com/sandervanvugt/cka/blob/master/mysapod.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: mysapod
spec:
  serviceAccountName: mysa
  containers:
  - name: alpine
    image: alpine:3.9
    command:
    - "sleep"
    - "3600"

После этого список подов будет доступен через API в mysapod

kubectl exec -it mysapod --sh
apk add curl
TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods --insecure

10.5 Cluster roles

Обычная роль действует в рамках namespace, кластерная роль распространяется на весь кластер.
Работает кластерная роль точно так же, как и обычная. Чтобы привязать пользователя или сервис-учётку к кластерной роли, нужен clusterRoleBinding.

# Список кластерных ролей
kubectl get clusterrole
# Опции (хорошая шпаргалка по синтаксису)
kubectl get clusterrole edit -o yaml |less
# Список привязок
kubectl get clusterrolebindings

10.6 Creating user accounts

В Кубере нет объектов-пользователей. Пользователь - это сертификат + ролевой доступ.

Чтобы создать «пользователя», нужно следующее:

  1. Сделать пару ключей.
  2. Сделать запрос на подпись сертификата (CSR)
  3. Подписать сертификат
  4. Создать конфиг, где эти ключи будут прописаны для доступа к кластеру
  5. Создать роль RBAC
  6. Создать привязку

Демо

kubectl create ns students
kubectl create ns staff
kubectl config get-contexts # один kubernetes-admin
# См. текущий контекст
less ~/.kube/config
 
# Создать юзера
sudo useradd -m -G sudo -s /bin/bash vasya
sudo passwd vasya
su - vasya
openssl genrsa -out vasya.key 2048
openssl req -new -key vasya.key -out vasya.csr -subj "/CN=vasya/O=k8s"
sudo openssl x509 -req -in vasya.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out vasya.crt -days 1800
 
# Добавить в конфиг креды нового юзера
mkdir /home/vasya/.kube
sudo cp -i /etc/kubernetes/admin.conf /home/vasya/.kube/config
sudo chown -R vasya: /home/vasya/.kube
kubectl config set-credentials vasya --client-certificate=/home/vasya/vasya.crt --client-key=/home/vasya/vasya.key
 
# Создать контекст по умолчанию для нового юзера
kubectl config set-context vasya-context --cluster=kubernetes --namespace=staff --user=vasya
# Переключиться на контекст постоянно
kubectl config use-context vasya-context
# Уже 2 контекста - админ и Вася
kubectl config get-contexts
# Следующая команда обломится, т. к. RBAC не сконфигурирован
kubectl get po

Настройка RBAC
Переключиться с Васи обратно на админа, т. к. у Васи нет полномочий работать с RBAC

su - user

Роль (https://github.com/sandervanvugt/cka/blob/master/staff-role.yaml)

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: staff
  name: staff
rules:
- apiGroups: ["", "extensions", "apps"]
  resources: ["deployments", "replicasets", "pods"]
  verbs: ["list", "get", "watch", "create", "update", "patch", "delete"] 

Привязка (https://github.com/sandervanvugt/cka/blob/master/rolebind.yaml)

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: staff-role-binding
  namespace: staff
subjects:
- kind: User
  name: vasya
  apiGroup: ""
roleRef:
  kind: Role
  name: staff
  apiGroup: ""
kubectl apply -f staff-role.yaml
kubectl apply -f rolebind.yaml
 
# Переключаемся обратно на Васю
su - vasya
# Вывод конфига, где видно, что контекст по умолчанию - vasya-context
kubectl config view
# Сделать тестовый деплой и вывести поды (на этот раз всё сработает)
kubectl create deploy nginx --image=nginx
kubectl get po

Дополнительно: Роль view-only

# Если Вася попытается посмотреть поды в ns default, то обломится
kubectl get po -n default

Роль (https://github.com/sandervanvugt/cka/blob/master/students-role.yaml):

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: students
rules:
- apiGroups: ["", "extensions", "apps"]
  resources: ["deployments", "replicasets", "pods"]
  verbs: ["list", "get", "watch"] 

Привязка (https://github.com/sandervanvugt/cka/blob/master/rolebindstudents.yaml):

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: students-role-binding
  namespace: default
subjects:
- kind: User
  name: vasya
  apiGroup: ""
roleRef:
  kind: Role
  name: students
  apiGroup: ""
kubectl apply -f students-role.yaml
kubectl apply -f rolebindstudents.yaml
 
# Теперь у Васи всё работает
kubectl get po -n default

Задача: создать роль в неймспейсе default на просмотр подов и дать ВСЕМ аутентифицированным пользователям возможность её использовать.

# См. справку, там есть пример
kubectl create role -h |less
# Создать роль
kubectl create role default-pod-viewer --verb=get,list,watch --resource=pods -n default
# Дальше в списке КЛАСТЕРНЫХ привязок есть system:basic-user.
kubectl get clusterrolebindings
# Если выполнить команду от его имени, то не получится (forbidden)
kubectl get pods --as system:basic-user
# Привязать роль к system:basic-user
kubectl create rolebinding default-pod-viewer --role=default-pod-viewer --user=system:basic-user -n default

11.1 Monitoring Kubernetes resources

Самое элементарное - kubectl get для базовой информации о состоянии и kubectl describe для более детальной. Если есть сервер метрик и собираются метрики, то можно использовать

kubectl top pods
kubectl top nodes

Для полноценного мониторинга нужны инструменты типа Prometheus и Grafana.

Установка сервера метрик:

# Если одна мастер-нода
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Если несколько (HA cluster)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/high-availability.yaml

:!: Нужно в yaml добавить --kubelet-insecure-tls, иначе работать с неподписанными сертификатами не будет. Но это применимо только в тестовой среде!

11.2 Troubleshooting flow

Ресурсы создаются сначала в ETCD. Уже с этого момента можно использовать команды

kubectl describe
kubectl events
 
# Дальше под стартует на запланированных узлах, перед стартом нужно скачать образ. Вывести список доступных образов на конкретной ноде можно
sudo crictl images
# После запуска уже можно смотреть в логи
kubectl logs [-f]
# и лезть в консоль.

kubectl get показывает базовый статус.

  • Pending: под существует в ETCD и ждёт, когда он может быть запущен на подходящей ноде. Пока нет подходящего узла для его запуска (taint/affinity/node selector и т. п.)
  • Running: работает нормально
  • Succeeded: отработал нормально (перезапуск не нужен)
  • Failed: один или несколько контейнеров в поде завершили работу с ошибкой и перезапущены не будут
  • Unknown: состояние неизвестно. Обычно так бывает при проблемах с сетью.
  • Completed: работа завершена.
  • CrashLoopBackOff: один или несколько контейнеров в поде завершают работу с ошибкой, но планировщик пытается перезапускать их

Что именно поломалось, надо выяснять у kubectl describe. Если это первичный контейнер пода, то см. логи. Если под работает, но не так, как нужно, тогда можно зайти в консоль.

11.4 Troubleshooting cluster nodes

# Основная информация о состоянии кластера
kubectl cluster-info
# ОЧЕНЬ подробная информация из всех кластерных логов (нужно парсить, чтобы что-то выцепить)
kubectl cluster-info dump
 
kubectl get nodes
kubectl get po -n kube-system
# Информация об узле, особое внимание на раздел Conditions, Taints, Allocated resources
kubectl describe node <nodename>
 
# На рабочей ноде - статус кублета (т. к. кублет - это самое главное, что есть на рабочей ноде)
systemctl status kubelet
 
# Проверить сертификат (редко нужно, но если рабочая нода давно работает и кластер не обновляется, сертификат может протухнуть)
sudo openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -text

11.5 Troubleshooting application access

Это выяснение проблем с подами, сервисами и ингрессом.
Сервис использует селектор для подключения к подам с совпадающим ярлыком. Ингресс подключается к сервису и берёт его селектор для подключения к соответствующим подам. Прежде всего нужно проверить ярлыки всех этих ресурсов.

# Вывести сервисы и конечные точки
kubectl get endpoints

Положим, видно, что у чего-то нет конечной точки. Далее уже нужно разбираться, что к чему, использовать curl и т. д.

learning/k8s-cka.txt · Последнее изменение: 30.07.2024 19:21 — 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki