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

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


learning:k8s-cka

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
learning:k8s-cka [28.03.2024 20:02] – [10.1 API ACCESS] viacheslavlearning:k8s-cka [27.03.2025 15:24] (текущий) – [Выжимка по апгрейду] viacheslav
Строка 1: Строка 1:
 +https://github.com/sandervanvugt/cka
 +
 +===== Установка кластера =====
 +Мин. требования к нодам:
 +  * Современная ОС типа Ubuntu 22.04
 +  * 2 ГБ памяти
 +  * 2 vCPU и более для контрольной ноды
 +  * Все ноды должны видеть друг друга по сети
 +
 +До установки кластера через kubeadm, нужно поставить
 +  * Контейнерный движок - containerd, CRI-O и т. д. (не является вопросом CKA, пример установки: https://github.com/sandervanvugt/cka/blob/master/setup-container.sh)
 +  * Kubernetes tools - kubeadm, kubelet, kubectl (также не является вопросом CKA, https://github.com/sandervanvugt/cka/blob/master/setup-kubetools.sh)
 +
 +Итак:
 +<code bash>
 +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
 +
 +</code>
 +Настройка автокомплита и алиаса: https://kubernetes.io/docs/reference/kubectl/quick-reference/#bash
 +
 +===== Настройка клиента, контекст =====
 +Для получения административного доступа к кластеру через клиента, файл ''/etc/kubernetes/admin.conf'' копируется в ''~/.kube/config''.\\
 +Под рутом для той же цели можно выполнить ''export KUBECONFIG=/etc/kubernetes/admin.conf''\\
 +Для более тонкой настройки нужно заводить спец. пользователя и настраивать ему права через управление ролями (RBAC).
 +
 +Контекст - шаблон конфигурации настроек клиента. При выборе контекста активируется определённая группа параметров клиента.
 +  * Кластер, к которому идёт подключение
 +  * Пространство имён (namespace)
 +  * Пользователь
 +
 +<code bash>
 +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
 +</code>
 +
 +===== Deployment =====
 +
 +Это стандартный способ развёртывания контейнеров масшабируемым способом. Масштабирование осуществляет механизм ReplicaSet. \\
 +Деплой предоставляет возможность RollingUpdate для обновлений без простоя.
 +<code bash>
 +# Императивный способ создания деплоя
 +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/    Running                      11s
 +pod/firstnginx-d8679d567-9sl2r   1/    Running                      11s
 +pod/firstnginx-d8679d567-vwkh7   0/    ContainerCreating            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/               2           11s
 +
 +NAME                                   DESIRED   CURRENT   READY   AGE
 +replicaset.apps/firstnginx-d8679d567                         11s
 +</code>
 +
 +===== DaemonSet =====
 +
 +Запускает по одному экземпляру приложения на каждой ноде. Обычно используется для всяких агентов мониторинга, kube-proxy.\\
 +Если нужно запустить демонсет на управляющих нодах, то надо конфигурировать toleration в нём для преодоления taint на мастер-нодах.
 +
 +<code bash>
 +# В пользовательском неймспейсе нет демонсетов. Смотрим во всех неймспейсах:
 +kubectl get ds -A
 +NAMESPACE     NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
 +kube-system   calico-node                                    3           kubernetes.io/os=linux   18h
 +kube-system   kube-proxy    3                                  3           kubernetes.io/os=linux   18h
 +
 +# Посмотреть, как настроен демонсет calico
 +kubectl get ds -n kube-system calico-node -o yaml |less
 +</code>
 +
 +Там описаны tolerations
 +<code yaml>
 +      tolerations:
 +      - effect: NoSchedule
 +        operator: Exists
 +      - key: CriticalAddonsOnly
 +        operator: Exists
 +      - effect: NoExecute
 +        operator: Exists
 +</code>
 +https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
 +
 +<code bash>
 +# Создать болванку для демонсета (create deploy, да)
 +kubectl create deploy mydaemon --image=nginx --dry-run=client -o yaml > mydeamon.yaml
 +</code>
 +
 +В получившемся файле mydeamon.yaml нужно
 +  - ''kind'' исправить на ''DaemonSet''
 +  - удалить строку ''replicas: 1''
 +  - удалить строку ''strategy: {}''
 +
 +<code bash>
 +kubectl apply -f mydeamon.yaml
 +
 +kubectl get ds
 +NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
 +mydaemon                                    2           <none>          27s
 +
 +kubectl get po -o wide
 +NAME                         READY   STATUS    RESTARTS   AGE   IP                NODE     NOMINATED NODE   READINESS GATES
 +firstnginx-d8679d567-4cxpj   1/    Running            51m   192.168.185.129   t-k8s3   <none>           <none>
 +firstnginx-d8679d567-9sl2r   1/    Running            51m   192.168.100.193   t-k8s2   <none>           <none>
 +firstnginx-d8679d567-vwkh7   1/    Running            51m   192.168.185.130   t-k8s3   <none>           <none>
 +mydaemon-cw7x8               1/    Running            75s   192.168.185.131   t-k8s3   <none>           <none>
 +mydaemon-srrb8               1/    Running            75s   192.168.100.194   t-k8s2   <none>           <none>
 +</code>
 +
 +===== 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 для подов в ст-сете.
 +
 +<code yaml>
 +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
 +</code>
 +
 +===== Запуск одиночных подов =====
 +
 +Это нужно только для тестирования, поиска проблем или анализа, потому что
 +  - Нет автоперезапуска
 +  - Нет балансировки
 +  - Нет защиты от простоя при обновлении
 +Так что в обычной ситуации нужно использовать деплой, д-сет и ст-сет.
 +<code bash>
 +# Справка
 +kubectl run -h |less
 +# Запустить под, который закончит работу через 1 ч.
 +kubectl run sleepy --image=busybox -- sleep 3600
 +</code>
 +
 +===== Init container =====
 +
 +Init container подготавливает почву для запуска основного контейнера в поде. Основной запускается после того, как отработал инициатор.\\
 +https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
 +
 +Простейший вариант
 +<file yaml 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"
 +</file>
 +https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-initialization/#create-a-pod-that-has-an-init-container
 +
 +<code bash>
 +kubectl apply -f initme.yaml
 +
 +kubectl get po
 +NAME                         READY   STATUS     RESTARTS   AGE
 +firstnginx-d8679d567-4cxpj   1/    Running    0          3h35m
 +firstnginx-d8679d567-9sl2r   1/    Running    0          3h35m
 +firstnginx-d8679d567-vwkh7   1/    Running    0          3h35m
 +init-demo                    0/1     Init:0/           25s
 +mydaemon-cw7x8               1/    Running    0          164m
 +mydaemon-srrb8               1/    Running    0          164m
 +
 +kubectl get po
 +...
 +init-demo                    0/1     PodInitializing            36s
 +
 +kubectl get po
 +...
 +init-demo                    1/1     Running            46s
 +</code>
 +
 +===== Масштабирование =====
 +
 +Деплой, д-сет и ст-сет могут быть масштабированы командой kubectl scale.\\
 +Есть ещё HorizontalPodAutoscaler, который автоматически увеличивает кол-во реплик при достижении некоего порога нагрузки (в рамках CKA это не рассматривается).
 +<code bash>
 +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/    Running            3h40m
 +pod/firstnginx-d8679d567-9sl2r   1/    Running            3h40m
 +pod/firstnginx-d8679d567-rmdqp   1/    Running            50s
 +pod/firstnginx-d8679d567-vwkh7   1/    Running            3h40m
 +pod/firstnginx-d8679d567-xvtm4   1/    Running            50s
 +
 +NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
 +deployment.apps/firstnginx   5/               5           3h40m
 +
 +NAME                                   DESIRED   CURRENT   READY   AGE
 +replicaset.apps/firstnginx-d8679d567                         3h40m
 +</code>
 +
 +===== Sidecar container =====
 +
 +Обычно под содержит один контейнер, но иногда бывают случаи, когда в поде нужен дополнительный контейнер для представления или модификации данных основного контейнера. Виды таких контейнеров-спутников:
 +  * Sidecar: доп. функционал к основному контейнеру
 +  * Ambassador: прокси для доступа извне
 +  * Adapter: обрабатывает вывод основного контейнера
 +
 +Тома, подключенные к поду, используются как общее хранилище. Основной контейнер пишет туда, а сайдкар читает это.\\
 +Том может быть подключен как через PVC, так и непосредственно.
 +
 +<file yaml 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" ]
 +</file>
 +
 +<code bash>
 +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
 +</code>
 +
 +===== Pod volumes =====
 +
 +Pod volumes - это часть спецификации пода, где прямо в манифесте пода жёстко заданы настройки хранилища. Это нормально, но негибко. Так могут быть заданы любые типы хранилища. Также, для монтирования pod volumes может использоваться ConfigMap.
 +
 +Типы хранилищ для подключения
 +<code bash>
 +kubectl explain pod.spec.volumes |less
 +</code>
 +
 +Пример самого простого общего хранилища - emptyDir. Это временная общая папка, существующая только когда существует под.
 +<file yaml 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: {}
 +</file>
 +
 +Если запустить этот манифест и создать файл в /centos1 первого контейнера, то во втором контейнере в /centos2 этот файл будет виден.
 +<code bash>
 +kubectl exec -it morevol -c centos1 -- touch /centos1/centos1file
 +kubectl exec -it morevol -c centos2 -- ls /centos2
 +centos1file
 +</code>
 +
 +===== Persistent volume (PV) =====
 +
 +Могут быть созданы вручную или автоматически (Storage class + Storage provisioner). Поды не могут подключать PV непосредственно, для подключения нужна заявка (PVC, persistent volume claim).
 +
 +PV объёмом 2 ГБ, локальное (что в кластере бессмысленно) по каталоге /mydata, режим доступа ReadWriteOnce.
 +<file yaml pv.yaml>
 +kind: PersistentVolume
 +apiVersion: v1
 +metadata:
 +  name: pv-volume
 +  labels:
 +      type: local
 +spec:
 +  storageClassName: demo
 +  capacity:
 +    storage: 2Gi
 +  accessModes:
 +    - ReadWriteOnce
 +  hostPath:
 +    path: "/mydata"
 +</file>
 +storageClassName используется как ярлык, без него PVC не сможет подключиться к PV.
 +
 +<code bash>
 +# Проверка
 +kubectl get pv
 +NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
 +pv-volume   2Gi        RWO            Retain           Available           demo                    38s
 +</code>
 +
 +===== Persistent volume claim (PVC) =====
 +
 +Позволяет поду подключиться к PV, некая заявка на предоставление места на диске. Указывается в манифесте пода.
 +
 +Если не будет указан storageClassName, то PVC будет в состоянии подключиться к PV и будет в состоянии pending.
 +<file yaml pvc.yaml>
 +kind: PersistentVolumeClaim
 +apiVersion: v1
 +metadata:
 +  name: pv-claim
 +spec:
 +  storageClassName: demo
 +  accessModes:
 +    - ReadWriteOnce
 +  resources:
 +    requests:
 +      storage: 1Gi
 +</file>
 +
 +<code bash>
 +# Проверка
 +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
 +</code>
 +Видно, что размер PVC 2 ГБ, несмотря на то, что требовался 1 ГБ. Но нельзя запросить половину объёма от PV, поэтому размер 2 ГБ.
 +
 +===== Подключение PV к поду =====
 +
 +В spec.volumes задан pv-storage, который использует pv-claim и монтируется в ''/usr/share/nginx/html''. В манифесте пода не указано ничего, что касается каких-либо параметров хранилища, что совершенно отделяет его настройки от настроек хранилища (decoupling).
 +
 +<file yaml 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
 +</file>
 +
 +<code bash>
 +# После запуска пода пишем на PV
 +kubectl exec -it pv-pod -- touch /usr/share/nginx/html/hello.html
 +</code>
 +Так как PV смотрит на локальную ФС, теперь надо понять, куда это дело записалось.
 +<code bash>
 +# Выяснить путь (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/    Running            5m11s   192.168.185.134   t-k8s3   <none>           <none>
 +</code>
 +Под запустился на 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 может быть задан по умолчанию
 +<code bash>
 +kubectl patch storageclass my-sc -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
 +</code>
 +Чтобы предоставлять хранилище в пользование, для 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.
 +
 +На мастер-ноде
 +<code bash>
 +sudo apt install nfs-server -y
 +sudo mkdir /nfsexport
 +sudo echo "/nfsexport *(rw,no_root_squash)" > /etc/exports
 +sudo systemctl restart nfs-server
 +</code>
 +
 +На остальных нодах
 +<code bash>
 +sudo apt install nfs-client -y
 +# Проверить, видят ли они NFS-шару
 +showmount -e <control node IP>
 +</code>
 +
 +На мастер-ноде
 +<code bash>
 +# Установить 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
 +</code>
 +Провайдер появляется в списке подов.
 +
 +Дальше создаётся PVC. storageClassName должен соответствовать имени storageClass (список - ''kubectl get storageclass'')
 +<code yaml>
 +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
 +</code>
 +После применения создаётся 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 нужно сделать по умолчанию:
 +<code bash>
 +kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
 +</code>
 +https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/
 +
 +После применения патча PVC без storageClassName будут успешно привязаны.
 +<code bash>
 +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
 +</code>
 +
 +===== Configmaps/secrets as volumes =====
 +
 +Configmap и secret по сути одно и то же, только секрет зашифрован в base64. Configmap используется для хранения переменных окружения, параметров запуска или файлов конфигурации. Когда конфиг хранится внутри configmap или secret, он монтируется как том внутри контейнера. Есть ограничение по объёму - 1 МБ. Если нужно больше, то придётся использовать реальные PV.
 +
 +<code bash>
 +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
 +</code>
 +
 +spec.template.spec - добавить раздел volumes: и внутри containers: сделать volumeMounts:
 +
 +<code yaml>
 +    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
 +</code>
 +
 +Именование важно: имя volumeMount ссылается на имя тома, а тот, в свою очередь, на имя configMap. Проверяем:
 +<code bash>
 +kubectl exec webserver-76d44586d-l7wzz -- cat /usr/share/nginx/html/index.html
 +Hello world
 +</code>
 +
 +===== Networking =====
 +
 +В k8s сетевое взаимодействие работает на несольких уровнях:
 +  * Между контейнерами - IPC (межпроцессное взаимодействие), это как внутри одного компьютера процессы взаимодействуют друг с другом.
 +  * Между подами - через сетевой плагин.
 +  * Между подами и сервисами - через ресурсы сервиса.
 +  * Между внешними пользователями и сервисами - с помощью сервисов и Ingress.
 +
 +Ноды подключены к внешней сети и также имеют виртуальную кластерную сеть. В кластерной сети работают сервисы, которые балансируют нагрузку на входящие в них поды. У подов своя сеть.
 +Для внешнего пользователя получить доступ к кластеру можно двумя способами:
 +  - Обращение через DNS на внешний балансировщик, который пробрасывает запросы на определённый порт какой-либо ноды кластера. Сервис в этом случае должен быть настроен в режиме NodePort (публикуется определённый порт на всех нодах кластера во внешней сети).
 +  - Обращение на 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'' делает то же самое, но, вероятно, менее удобно.
 +
 +<code bash>
 +kubectl create deploy webshop --image=nginx --replicas=3
 +kubectl expose deploy webshop --type=NodePort --port=80
 +kubectl describe svc webshop
 +...
 +Selector: app=webshop
 +</code>
 +
 +С помощью селектора сервис подключается к одноимённым ярлыкам подов (Labels: app=workshop).
 +Теперь приложение доступно по адресу любой ноды (в т. ч. управляющей) + порт (ip:nodePort).
 +
 +===== Ingress controller =====
 +
 +Ингресс предоставляет внешний доступ к сервисам кластера. Работает с внешним DNS, чтобы доступ был по URL. Состоит из 2 частей: балансировщик, доступный из внешней сети и API-ресурс, контактирующий с сервисами, чтобы обнаружить поды за ними. Ingress надо ставить отдельно, его нет в стандартной установке Кубера.
 +Ингресс работает только с HTTP/HTTPS. Использует селектор в сервисах, чтобы подключаться к подам.
 +
 +Трафик управляется согласно правилам, описанным в ингресс-ресурсе. Ингресс может быть настроен для следующих функций:
 +  - Сделать сервисы доступными по внешним URL
 +  - Балансировка трафика
 +  - Терминирование SSL/TLS
 +  - Offer name based virtual hosting
 +
 +<code bash>
 +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
 +</code>
 +
 +Если посмотреть внутрь созданного ингресса (kubectl describe ingress nginxsvc), то там не будет селектора - ингресс работает через правило.\\ Селектор играет роль в сервисе, поэтому если выдаются 500-е ошибки, надо проверять селектор сервиса.
 +
 +Узнать порт доступа к ингрессу извне:
 +<code bash>
 +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
 +</code>
 +Теперь после заведения DNS-записи можно обращаться к http://nginxsvc.ru:32452 или https://nginxsvc.ru:30462
 +
 +===== Configuring Ingress =====
 +<code bash>
 +# Справка с примерами
 +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
 +</code>
 +
 +==== IngressClass ====
 +В одном кластере могут находиться разные ингресс-контроллеры каждый со своей конфигурацией.
 +Контроллер задаётся в IngressClass.
 +При создании ингресс-правил опция ''%%--class%%'' должна быть использована для задания роли на определённом ингресс-контроллере.
 +Если ''%%--class%%'' не используется, тогда должен быть задан класс по умолчанию: это ''%%ingressclass.kubernetes.io/is-default-class: "true"%%'' в аннотациях.
 +<code bash>
 +# После создания ингресс-контроллера создаётся API-ресурс IngressClass, содержимое которого можно посмотреть командой
 +kubectl get ingressclass -o yaml
 +
 +# Отредактировать уже существующий ингресс-класс nginx, сделав его по умолчанию:
 +kubectl edit ingressclass nginx
 +</code>
 +
 +<code yaml>
 +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
 +...
 +</code>
 +
 +===== Port forwarding =====
 +
 +Нужен для тестирования доступа к приложению без возни с сервисами и ингрессом.
 +<code bash>
 +# Редирект с 1234 на 80
 +kubectl port-forward mypod 1234:80
 +</code>
 +По умолчанию запускается на переднем плане, поэтому чтобы освободить консоль, можно нажать Ctrl+Z или сразу запускать команду с & в конце.
 +Например
 +<code bash>
 +kubectl port-forward mypod 1234:80 &
 +curl localhost:1234
 +</code>
 +
 +===== Lab 5 =====
 +<code bash>
 +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
 +</code>
 +
 +===== Cluster nodes =====
 +<code bash>
 +# Информация по кублету
 +systemctl status kubelet
 +# Также см. /var/log или journalctl
 +# Основная информация о ноде
 +kubectl describe node $(hostname)
 +# Если установлен сервер метрик, то получить информацию о загрузке процессора/памяти можно
 +kubectl top nodes
 +</code>
 +
 +crictl - утилита для получения информации о контейнерах (вместо docker/podman).
 +Чтобы использовать её, нужно задать runtime-endpoint и image-endpoint. Самый удобный способ - на нодах, где планируется использование crictl, отредактировать файл ''/etc/crictl.yaml'' (см. пример в репе ''cka/crictl.yaml'').
 +
 +Всё это на конкретной ноде. Без sudo не работает, прав не хватит.
 +<code bash>
 +# Выдать список контейнеров
 +sudo crictl ps
 +# Выдать список подов
 +sudo crictl pods
 +# В списке контейнеров имена неуникальные. Если что, то надо смотреть детальную информацию по контейнеру по его ID
 +sudo crictl inspect 82e883gf8v8re
 +# Список образов
 +sudo crictl images
 +# Скачать образ mysql с docker.io
 +sudo crictl pull docker.io/library/mysql
 +</code>
 +Crictl - это только средство для контейнеров, оно не имеет полного функционала docker/podman.
 +
 +===== Static pods =====
 +
 +Это поды, запущенные непосредственно процессом kubelet на ноде. Таким образом работают основные сервисы Кубера. Админ может добавить такой под, скопировав манифест в каталог ''/etc/kubernetes/manifests''. Этот каталог можно поменять на другой, указав его в параметре staticPodPath файла ''/var/lib/kubelet/config.yaml'' и перезапустив кублет.
 +<code bash>
 +sudo systemctl restart kubelet
 +</code>
 +Никогда не нужно делать это на управляющей/контрольной ноде, т. к. поды Кубера перестанут работать! На рабочих нодах статических подов нет, поэтому запускать дополнительные статические поды нужно только на них.
 +
 +<code bash>
 +# На контрольной ноде генерим манифест пода
 +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
 +...
 +</code>
 +
 +===== Managing node state =====
 +<code bash>
 +kubectl cordon # больше не принимать нагрузку
 +kubectl drain # больше не принимать нагрузку и убрать все поды
 + --ignore-daemonsets # и демонсеты тоже
 + --delete-emptydir-data # удалить данные из томов emptyDir
 +kubectl uncordon # вернуть ноду в список доступных для распределения нагрузки
 +</code>
 +cordon/drain ставят taint на ноду, что и даёт эффект того, что на неё нельзя распределить нагрузку.
 +
 +Поды автоматически после возвращения ноды не распределяются.
 +
 +===== Managing node services =====
 +
 +На рабочей ноде это кублет и контейнерный движок, управляемый systemd.
 +
 +<code bash>
 +# Состояние кублета\старт\стоп
 +sudo systemctl status\start\stop kubelet
 +# См. процесс кублета
 +ps aux |grep kubelet
 +# Процессы контейнерного движка
 +ps aux |grep containerd
 +</code>
 +
 +Если убить (kill) процесс kubelet, то он опять запустится, т. к. в юните кублета задано его перезапускать.
 +<code bash>
 +systemctl cat kubelet.service
 +...
 +[Service]
 +ExecStart=/usr/bin/kubelet
 +Restart=always
 +StartLimitInterval=0
 +RestartSec=10
 +...
 +</code>
 +Если просто остановить сервис (sudo systemctl stop kubelet), то он сам перезапускаться не будет.
 +
 +===== Metrics server =====
 +
 +Сервер метрик нужен для мониторинга нод и состояния подов. Его нужно установить отдельно.\\
 +https://github.com/kubernetes-sigs/metrics-server
 +<code bash>
 +# Установка (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
 +</code>
 +
 +Там в spec.template добавить --kubelet-insecure-tls
 +<code yaml>
 +    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
 +</code>
 +<code bash>
 +# После этого сервер метрик начинает работать и можно проверить ресурсы
 +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
 +</code>
 +
 +===== ETCD backup =====
 +
 +Etcd работает как статический под на мастер-ноде, его потеря значит потеря всей конфигурации кластера. Это делается под рутом, используя etcdctl, который нужно сначала установить.
 +<code bash>
 +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
 +</code>
 +Очень желательно бэкап держать в нескольких местах для надёжности.
 +
 +===== ETCD restore =====
 +<code bash>
 +# Можно вытереть пару деплоев, чтобы был виден результат восстановления
 +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
 +</code>
 +
 +===== 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).
 +<code bash>
 +v='v1.29'
 +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/$v/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
 +curl -fsSL https://pkgs.k8s.io/core:/stable:/$v/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
 +sudo apt-get update
 +</code>
 +
 +<code bash>
 +# 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
 +
 +# Парсер вывода apt-cache madison kubeadm (выводит 1.29.2-*)
 +ver=$(apt-cache madison kubeadm |head -1 |cut -d '|' -f2 |xargs |sed 's#-.*#-*#')
 +
 +# 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="$ver" && \
 +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
 +</code>
 +
 +https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/#considerations-when-upgrading-etcd
 +<code bash>
 +# 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="$ver" kubectl="$ver" && \
 +sudo apt-mark hold kubelet kubectl
 +# Restart the kubelet:
 +sudo systemctl daemon-reload
 +sudo systemctl restart kubelet
 +# Uncordon the node
 +kubectl uncordon $(hostname)
 +</code>
 +
 +==== Upgrade worker nodes ====
 +<code bash>
 +sudo apt-mark unhold kubeadm && \
 +sudo apt-get update && sudo apt-get install -y kubeadm="$ver" && \
 +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="$ver" kubectl="$ver" && \
 +sudo apt-mark hold kubelet kubectl
 +# Restart the kubelet:
 +sudo systemctl daemon-reload
 +sudo systemctl restart kubelet
 +# На мастер-ноде: Uncordon the node
 +kubectl uncordon k3
 +</code>
 +
 +=== Выжимка по апгрейду ===
 +:!: Здесь не учитывается, что на кластере есть нагрузка и нет команд перевода нагрузки на другие ноды.
 +<code bash>
 +sudo -i
 +
 +# желаемая версия
 +v='v1.32'
 +
 +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/$v/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list
 +curl -fsSL https://pkgs.k8s.io/core:/stable:/$v/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
 +apt update
 +
 +ver=$(apt-cache madison kubeadm |head -1 |cut -d '|' -f2 |xargs |sed 's#-.*#-*#')
 +
 +apt-mark unhold kubeadm && \
 +apt-get install -y kubeadm="$ver" && \
 +apt-mark hold kubeadm
 +
 +#################################
 +kubeadm upgrade plan # для мастера, оттуда выполнить команду kubeadm upgrade apply vX.XX.X
 +kubeadm upgrade node # для рабочих нод
 +#################################
 +
 +apt-mark unhold kubelet kubectl && \
 +apt-get install -y kubelet="$ver" kubectl="$ver" && \
 +apt-mark hold kubelet kubectl
 +
 +systemctl daemon-reload
 +systemctl restart kubelet
 +</code>
 +
 +===== Highly available cluster =====
 +
 +2 варианта:
 +  - Stacked control plane nodes - когда control plane и etcd работают на одной ноде. Оптимально иметь 3 и более узлов.
 +  - External etcd cluster - etcd в отдельном кластере. Нужно больше узлов.
 +Для отказоустойчивого кластера нужен балансировщик, например, keepalived.
 +
 +Схема примерно такая: на каждой мастер-ноде стоит
 +  * API-сервер:6443
 +  * haproxy:8443, обеспечивающий доступ к API
 +  * keepalived c VIP:8443, который балансирует запросы к мастер-нодам. Клиент kubectl обращается к VIP keepalived.
 +
 +<code bash>
 +# 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)
 +</code>
 +На всех нодах скопировать /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, которое указывает на соответствующий ярлык на узле и запускает под там.
 +<code bash>
 +# Задать ярлык на ноде
 +kubectl label nodes worker1 disktype=ssd
 +# Удалить ярлык
 +kubectl label nodes worker1 disktype-
 +</code>
 +
 +Пример манифеста пода
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: nginx
 +spec:
 +  containers:
 +  - name: nginx
 +    image: nginx
 +    imagePullPolicy: IfNotPresent
 +  nodeSelector:
 +    disktype: ssd
 +</code>
 +
 +В pod.spec есть ещё и nodeName, где можно задать конкретное имя узла, на котором всегда будет запускаться этот под. Но так не рекомендуется делать - если узел выйдет из строя, то под не запустится больше нигде.
 +
 +===== 8.3 Affinity =====
 +
 +Более продвинутые правила размещения подов.
 +  * Node affinity - правило на узле, которое размещает там поды с совпадающими ярлыками.
 +  * Inter-pod affinity - правило, по которому одни поды размещаются на том же узле, что и другие.
 +  * Anti-affinity - применимо только к подам, не к узлам.
 +
 +Т. е.,
 +  - Под с node-affinity-ярлыком key=value будет размещён только на узлах, имеющих совпадающий ярлык.
 +  - Под с pod-affinity-ярлыком key=value будет размещён только на узлах, имеющих поды с совпадающим ярлыком.
 +
 +Есть 2 режима работы affinity:
 +  - requiredDuringSchedulingIgnoredDuringExecution - требование соответствия ограничению
 +  - preferredDuringSchedulingIgnoredDuringExecution - предпочтение соответствия ограничению (если не может быть удовлетворено - игнорируется)
 +
 +Affinity применяется только к планируемым подам, не к работающим. Те, которые уже работают, меняться не будут.
 +
 +Affinity - это не просто соответствие ярлыков, а ещё и логика.\\
 +Здесь выбираются ноды, где тип равен blue или green.
 +
 +<code yaml>
 +spec:
 +  affinity:
 +    podAffinity:
 +      requiredDuringSchedulingIgnoredDuringExecution:
 +        nodeSelectorTerms:
 +        - matchExpressions:
 +          - key: type
 +            operator: In
 +            values:
 +            - blue
 +            - green
 +</code>
 +
 +Здесь - ноды, где задан ключ storage.
 +<code yaml>
 +        nodeSelectorTerms:
 +        - matchExpressions:
 +          - key: storage
 +            operator: Exists
 +</code>
 +
 +При использовании pod affinity/anti-affinity свойство topologyKey обязательно к указанию.
 +<code yaml>
 +spec:
 +  affinity:
 +    podAffinity:
 +      requiredDuringSchedulingIgnoredDuringExecution:
 +      - labelSelector:
 +          matchExpressions:
 +          - key: security
 +            operator: In
 +            values:
 +            - S1
 +        topologyKey: failure-domain.beta.kubernetes.io/zone
 +</code>
 +
 +topologyKey ссылается на ярлык, который прописан на ноде. Обычно он имеет наклонную черту, например, ''kubernetes.io/host''.\\
 +topologyKey разрешает запускать поды только на узлах, имеющих этот ключ. Это позволяет создавать сегментировать кластер на зоны, где запускается нагрузка. Если topologyKey не найден на ноде, то он игнорируется в настройках pod affinity.
 +
 +redis-with-pod-affinity.yaml - интересный пример. В манифесте написано не запускать под, если узел уже имеет под с ярлыком app=store, вместе с тем, под сам имеет этот ярлык. Это приведёт к тому, что на одной ноде будет запущен только один под, т. к. второй уже не уживётся с таким же экземпляром на одном узле.
 +<code yaml>
 +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
 +</code>
 +
 +web-with-pod-affinity.yaml - то же самое: один web-server не будет работать с другим на одной и той же ноде, но зато будет запускаться только там, где есть под с ярлыком app=store, т. е. с redis-cache.
 +<code yaml>
 +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
 +</code>
 +
 +===== 8.4 Taints / tolerations =====
 +
 +Taint применяется к узлам. Это метка, которая препятствует запуску любых подов, если у них нет соответствующего toleration.\\
 +Toleration применяется к подам. Это метка, которая позволяет (но не предписывает!) поду запуститься на узле с соответствующим taint.\\
 +Если affinity применяется для того, чтобы привязать поды к конкретным узлам, то taint - чтобы исключить их запуск на узлах.\\
 +Taints и tolerations гарантируют, что поды не запускаются на неподходящих узлах, таким образом гарантируя, что на выделенных узлах запускаются только поды для конкретных задач.
 +
 +Есть 3 типа taints:
 +  - NoSchedule: не размещать новые поды
 +  - PreferNoSchedule: не размещать новые поды, пока есть альтернатива
 +  - NoExecute - не размещать и выгнать уже существующие
 +
 +Taints задаются разными способами.\\
 +На контрольных нодах тейнт задан по умолчанию, чтобы там не размещалась рабочая нагрузка.\\
 +Команды kubectl drain и kubectl cordon создают тейнт на целевом узле.\\
 +Тейнт может быть установлен автоматически кластером в определённых условиях, например, из-за:
 +  - нехватки места на ноде
 +  - нехватки памяти
 +  - нехватки доступных ID процессов
 +  - невозможность для планирования
 +  - недоступность по сети
 +
 +И эти автоматические тейнты могут быть преодолены соответствующими толерами, но этого крайне не рекомендуется делать, т.к. когда эти тейнты ставятся, значит, с узлом проблемы.
 +
 +<code bash>
 +# Тейнт может быть задан админом:
 +kubectl taint nodes worker1 key1=value1:NoSchedule
 +# Удалить тейнт:
 +kubectl taint nodes worker1 key1=value1:NoSchedule-
 +</code>
 +
 +Толер необходим для запуска подов на узле с тейнтом, например, так работают поды Кубера на мастер-ноде.
 +Ключ и значение у тейнтов и толеров позволяют задавать тонкие настройки запуска. Например, 
 +<code bash>
 +kubectl taint nodes worker1 storage=ssd:NoSchedule
 +</code>
 +позволит запускаться на узле worker1 только тем подам, у которых есть толер storage=ssd.
 +Если у пода есть толер storage=hdd, то он тут не запустится.
 +
 +Толер в манифесте пода задаётся ключом, оператором и значением:
 +<code yaml>
 +spec:
 +  tolerations:
 +  - key: "storage"
 +    operator: "Equal"
 +    value: "ssd"
 +</code>
 +Equal - оператор по умолчанию. Также часто используется ''%%"Exists"%%'', в этом случае value игнорируется.
 +
 +===== 8.6 Limitrange / quota =====
 +
 +LimitRange - API-объект, ограничивающий использование ресурсов подом или контейнером в пространстве имён. 3 опции:
 +  - type: к чему применяется - к подам или контейнерам
 +  - defaultRequest: сколько ресурсов запросит приложение по умолчанию
 +  - default: максимум ресурсов, которые может испольовать приложение
 +
 +Quota - API-объект, ограничивающий количество доступных ресурсов в пространстве имён. Если пространство имён настроено с квотой, приложения в этом неймспейсе обязаны быть с настройками ресурсов в ''pod.spec.containers.resources''.
 +
 +Т. е., LimitRange задаёт лимиты для каждого приложения, а Quota - для целого пространства имён (всех приложений в нём).
 +<code bash>
 +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
 +</code>
 +
 +===== 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)
 +<code bash>
 +kubectl get all -n kube-system
 +</code>
 +IP-диапазон для кластерной сети (см. ''%%--service-cluster-ip-range%%'')
 +<code bash>
 +ps aux |grep api
 +</code>
 +
 +===== 9.2 Service auto registration =====
 +
 +Кубер запускает поды coredns в системном пространстве имён как внутренние DNS-сервера.\\
 +Эти поды опубликованы через сервис kube-dns и любой под в Кубере может обратиться к coredns.\\
 +Поды автоматически настраиваются с IP-адресом сервиса kube-dns в качестве своего DNS-сервера.\\
 +В результате, поды могут обращаться к сервисам по имени.
 +
 +Если обращение идёт к сервису в том же пространстве имён, можно обращаться к нему по короткому имени.\\
 +Если в другом - то по FQDN (''servicename.namespace.svc.clustername'').\\
 +Имя кластера по умолчанию - cluster.local, записано в Corefile у coredns (хранится как ConfigMap).\\
 +Проверить можно
 +<code bash>
 +kubectl get cm -n kube-system coredns -o yaml
 +</code>
 +
 +Тест
 +<code bash>
 +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
 +</code>
 +
 +===== 9.3 Network policies =====
 +
 +Изначально между подами нет сетевых ограничений и они могут свободно общаться даже если находятся в разных пространствах имён. Чтобы регулировать трафик, существуют сетевые политики. Чтобы политики работали, они должны поддерживаться сетевым плагином. Например, плагин weave не поддерживает сетевых политик.\\
 +Если политика есть и правило не совпадает, то трафик запрещается.\\
 +Если политики нет, трафик разрешается.
 +
 +В сетевой политике есть 3 разных идентификатора:
 +  - Поды (podSelector): разрешает доступ к подам. Само собой, что под не может заблокировать доступ к самому себе.
 +  - Пространства имён (namespaceSelector): разрешает доступ к пространствам имён.
 +  - Диапазон 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 не будет.
 +<code yaml>
 +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"
 +</code>
 +
 +<code bash>
 +# Публикуем под 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" значит, что исходящий трафик не ограничен.
 +</code>
 +
 +===== 9.4 Network policies (namespaces) =====
 +
 +Network policy - это РАЗРЕШЕНИЕ трафика, но если она есть, то всё остальное запрещается.\\
 +Пример: https://github.com/sandervanvugt/cka/blob/master/nwp-lab9-2.yaml\\
 +Разрешить любые поды в пространстве имён default.\\
 +Следовательно, все остальные доступа не получат.\\
 +<code yaml>
 +kind: NetworkPolicy
 +apiVersion: networking.k8s.io/v1
 +metadata:
 +  namespace: default
 +  name: deny-from-other-namespaces
 +spec:
 +  podSelector:
 +    matchLabels:
 +  ingress:
 +  - from:
 +    - podSelector: {}
 +</code>
 +Теперь, если обращаться даже по FQDN из другого пространства имён, ничего не выйдет.
 +<code bash>
 +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 # работает
 +</code>
 +https://github.com/sandervanvugt/cka/blob/master/lesson9lab.yaml
 +
 +Применить политику: работает в ''namespace: restricted'', можно подключаться из ''namespace: default'' с подов с ярлыком ''access=yes''. Важно - namespaceSelector и podSelector в одной связке, не под разными пунктами.
 +<code yaml>
 +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"
 +</code>
 +<code bash>
 +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 # не работает (что и требуется)
 +</code>
 +
 +===== 10.1 API access =====
 +
 +Прямого доступа к ETCD нет, доступ осуществляется через kube-apiserver.
 +Права доступа описываются в Roles и clusterRoles.
 +
 +Есть 2 способа доступа к API:
 +  - kubectl. Это клиентская программа, где настройки описываются стандартно в ''.kube/config''.\\ Там описана PKI (public key infrastructure, инфраструктура открытых ключей). Пользователей как таковых нет в Кубере, поэтому подписанные PKI-сертификаты и есть "пользователи".
 +  - Приложения. К примеру, некоторые поды берут информацию от 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 (запуск пода под нерутовым пользователем)
 +
 +Сек. контекст может быть задан на уровне пода или на уровне контейнера. См.
 +<code bash>
 +kubectl explain pod.spec.securityContext
 +kubectl explain pod.spec.containers.securityContext
 +</code>
 +Набор параметров неодинаковый.\\
 +Если одни и те же настройки заданы одновременно и для пода, и для контейнера, приоритет у более специфических настроек (контейнера). А лучше вообще избегать таких ситуаций.\\
 +Пример (https://github.com/sandervanvugt/cka/blob/master/security-context.yaml)
 +<code 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
 +</code>
 +
 +===== 10.3 Using ServiceAccounts to Configure API Access =====
 +
 +В Кубере нет внутренних пользователей. Они берутся извне:
 +  * Определяются сертификатами X.509
 +  * Берутся из внешних источников (Google, AD, etc.)
 +Сервисные учётки используются для авторизации подов для получения доступа к специфическим ресурсам.
 +
 +<code bash>
 +# Сервисные учётки, существующие в текущем контексте
 +kubectl get sa
 +# Помимо стандартной, там есть ещё учётка NFS Storage Provisioner, потому что её нужно создавать PVs, а для этого нужен доступ к API.
 +
 +#Все сервис-учётки:
 +kubectl get sa -A
 +# Найти запись об используемой сервис-учётке можно в свойствах пода
 +kubectl describe po <podname> |grep -i serviceaccount
 +</code>
 +
 +===== 10.4 RBAC =====
 +
 +Role-based access control - ролевой доступ. Имеет 3 компонента:
 +
 +1) Роль: используется в пространстве имён и задаёт права доступа к ресурсам в этом пространстве имён.
 +<code bash>
 +kubectl create role -h |less
 +kubectl get roles
 +kubectl get roles -A # какие вообще существуют, может пригодиться для изучения
 +kubectl get role leader-locking-nfs-subdir-external-provisioner -o yaml |less
 +</code>
 +
 +2) Привязка роли (roleBindings). Роль сама по себе не указывает, кто может её использовать.\\
 +roleBindings подключает пользователей или сервис-аккаунты к ролям.
 +<code bash>
 +kubectl create rolebinding -h |less
 +kubectl get rolebinding -A
 +kubectl get rolebinding ingress-nginx -n ingress-nginx -o yaml |less
 +</code>
 +RoleRef указывает на роль, к которой идёт привязка, а subjects - кто привязан.
 +
 +3) Сервисные учётки (ServiceAccounts).\\
 +Сервис-учётка авторизует поды, чтобы те могли получить информацию от API.\\
 +Все поды имеют сервис-учётку по умолчанию, которая предоставляет им минимальный уровень доступа.\\
 +Если этого недостаточно, нужно сделать отдельную сервисную учётку.\\
 +У сервис-учётки нет особой конфигурации, они просто используются в привязках, чтобы получить доступ к роли.
 +
 +Демо\\
 +Сделать под alpine (https://github.com/sandervanvugt/cka/blob/master/mypod.yaml)
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: mypod
 +spec:
 +  containers:
 +  - name: alpine
 +    image: alpine:3.9
 +    command:
 +    - "sleep"
 +    - "3600"
 +</code>
 +<code bash>
 +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.
 +</code>
 +
 +Создаём сервис-учётку (https://github.com/sandervanvugt/cka/blob/master/mysa.yaml)
 +<code yaml>
 +apiVersion: v1
 +kind: ServiceAccount
 +metadata:
 +  name: mysa
 +</code>
 +Создаём роль (https://github.com/sandervanvugt/cka/blob/master/list-pods.yaml)
 +<code yaml>
 +apiVersion: rbac.authorization.k8s.io/v1
 +kind: Role
 +metadata:
 +  name: list-pods
 +  namespace: default
 +rules:
 +  - apiGroups:
 +    - ''
 +    resources:
 +    - pods
 +    verbs:
 +    - list
 +</code>
 +Или из командной строки:
 +<code bash>
 +kubectl create role list-pods --verb=list --resource=pods -n default
 +</code>
 +
 +Создаём привязку (https://github.com/sandervanvugt/cka/blob/master/list-pods-mysa-binding.yaml):
 +<code 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
 +</code>
 +
 +В поде надо поменять сервис-учётку (https://github.com/sandervanvugt/cka/blob/master/mysapod.yaml):
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: mysapod
 +spec:
 +  serviceAccountName: mysa
 +  containers:
 +  - name: alpine
 +    image: alpine:3.9
 +    command:
 +    - "sleep"
 +    - "3600"
 +</code>
 +
 +После этого список подов будет доступен через API в mysapod
 +<code bash>
 +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
 +</code>
 +
 +===== 10.5 Cluster roles =====
 +
 +Обычная роль действует в рамках namespace, кластерная роль распространяется на весь кластер.\\
 +Работает кластерная роль точно так же, как и обычная. Чтобы привязать пользователя или сервис-учётку к кластерной роли, нужен clusterRoleBinding.
 +<code bash>
 +# Список кластерных ролей
 +kubectl get clusterrole
 +# Опции (хорошая шпаргалка по синтаксису)
 +kubectl get clusterrole edit -o yaml |less
 +# Список привязок
 +kubectl get clusterrolebindings
 +</code>
 +
 +===== 10.6 Creating user accounts =====
 +
 +В Кубере нет объектов-пользователей. Пользователь - это сертификат + ролевой доступ.
 +
 +Чтобы создать "пользователя", нужно следующее:
 +  - Сделать пару ключей.
 +  - Сделать запрос на подпись сертификата (CSR)
 +  - Подписать сертификат
 +  - Создать конфиг, где эти ключи будут прописаны для доступа к кластеру
 +  - Создать роль RBAC
 +  - Создать привязку
 +
 +Демо
 +<code bash>
 +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
 +</code>
 +
 +Настройка RBAC\\
 +Переключиться с Васи обратно на админа, т. к. у Васи нет полномочий работать с RBAC
 +<code bash>
 +su - user
 +</code>
 +
 +Роль (https://github.com/sandervanvugt/cka/blob/master/staff-role.yaml)
 +<code 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"
 +</code>
 +
 +Привязка (https://github.com/sandervanvugt/cka/blob/master/rolebind.yaml)
 +<code 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: ""
 +</code>
 +
 +<code bash>
 +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
 +</code>
 +
 +Дополнительно: Роль view-only
 +<code bash>
 +# Если Вася попытается посмотреть поды в ns default, то обломится
 +kubectl get po -n default
 +</code>
 +
 +Роль (https://github.com/sandervanvugt/cka/blob/master/students-role.yaml):
 +<code 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"
 +</code>
 +
 +Привязка (https://github.com/sandervanvugt/cka/blob/master/rolebindstudents.yaml):
 +<code 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: ""
 +</code>
 +<code bash>
 +kubectl apply -f students-role.yaml
 +kubectl apply -f rolebindstudents.yaml
 +
 +# Теперь у Васи всё работает
 +kubectl get po -n default
 +</code>
 +
 +Задача: создать роль в неймспейсе default на просмотр подов и дать ВСЕМ аутентифицированным пользователям возможность её использовать.
 +<code bash>
 +# См. справку, там есть пример
 +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
 +</code>
 +
 +===== 11.1 Monitoring Kubernetes resources =====
 +
 +Самое элементарное - ''kubectl get'' для базовой информации о состоянии и ''kubectl describe'' для более детальной.
 +Если есть сервер метрик и собираются метрики, то можно использовать
 +<code bash>
 +kubectl top pods
 +kubectl top nodes
 +</code>
 +Для полноценного мониторинга нужны инструменты типа Prometheus и Grafana.
 +
 +Установка сервера метрик:
 +<code bash>
 +# Если одна мастер-нода
 +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
 +</code>
 +:!: Нужно в yaml добавить ''%%--kubelet-insecure-tls%%'', иначе работать с неподписанными сертификатами не будет. Но это применимо только в тестовой среде!
 +
 +===== 11.2 Troubleshooting flow =====
 +
 +Ресурсы создаются сначала в ETCD. Уже с этого момента можно использовать команды
 +<code bash>
 +kubectl describe
 +kubectl events
 +
 +# Дальше под стартует на запланированных узлах, перед стартом нужно скачать образ. Вывести список доступных образов на конкретной ноде можно
 +sudo crictl images
 +# После запуска уже можно смотреть в логи
 +kubectl logs [-f]
 +# и лезть в консоль.
 +</code>
 +
 +kubectl get показывает базовый статус.
 +  * Pending: под существует в ETCD и ждёт, когда он может быть запущен на подходящей ноде. Пока нет подходящего узла для его запуска (taint/affinity/node selector и т. п.)
 +  * Running: работает нормально
 +  * Succeeded: отработал нормально (перезапуск не нужен)
 +  * Failed: один или несколько контейнеров в поде завершили работу с ошибкой и перезапущены не будут
 +  * Unknown: состояние неизвестно. Обычно так бывает при проблемах с сетью.
 +  * Completed: работа завершена.
 +  * CrashLoopBackOff: один или несколько контейнеров в поде завершают работу с ошибкой, но планировщик пытается перезапускать их
 +
 +Что именно поломалось, надо выяснять у ''kubectl describe''. Если это первичный контейнер пода, то см. логи. Если под работает, но не так, как нужно, тогда можно зайти в консоль.
 +
 +===== 11.4 Troubleshooting cluster nodes =====
 +<code bash>
 +# Основная информация о состоянии кластера
 +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
 +</code>
 +
 +===== 11.5 Troubleshooting application access =====
 +
 +Это выяснение проблем с подами, сервисами и ингрессом.\\
 +Сервис использует селектор для подключения к подам с совпадающим ярлыком. Ингресс подключается к сервису и берёт его селектор для подключения к соответствующим подам. Прежде всего нужно проверить ярлыки всех этих ресурсов.
 +<code bash>
 +# Вывести сервисы и конечные точки
 +kubectl get endpoints
 +</code>
 +Положим, видно, что у чего-то нет конечной точки. Далее уже нужно разбираться, что к чему, использовать curl и т. д.
  

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki