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)
Итак:
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 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 71s v1.28.2
t-k8s3 Ready 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 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 нужно
- ''kind'' исправить на ''DaemonSet''
- удалить строку ''replicas: 1''
- удалить строку ''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 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
firstnginx-d8679d567-9sl2r 1/1 Running 0 51m 192.168.100.193 t-k8s2
firstnginx-d8679d567-vwkh7 1/1 Running 0 51m 192.168.185.130 t-k8s3
mydaemon-cw7x8 1/1 Running 0 75s 192.168.185.131 t-k8s3
mydaemon-srrb8 1/1 Running 0 75s 192.168.100.194 t-k8s2
===== StatefulSet =====
Подвид деплоя, предназначенный для stateful-приложений или для тех. кому нужно хотя бы одно из перечисленного:
* Стабильные и уникальные сетевые имена
* Стабильное постоянное хранилище данных
* Упорядоченное развёртывание и масштабирование
* Упорядоченное и автоматическое "плавающее обновление" (rolling updates)
^Deployment ^StatefulSet ^
|Для Stateless-приложений |Для Stateful-приложений |
|Все поды создаются параллельно |Поды создаются последовательно один за другим (pod-0..pod-X) |
|Поды удаляются в случайном порядке |Поды удаляются в обратном порядке (pod-X..pod-0) |
|Поды имеют случайные имена |Поды имеют чётко определённые имена - |
|Все поды используют один и тот же 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
===== Запуск одиночных подов =====
Это нужно только для тестирования, поиска проблем или анализа, потому что
- Нет автоперезапуска
- Нет балансировки
- Нет защиты от простоя при обновлении
Так что в обычной ситуации нужно использовать деплой, д-сет и ст-сет.
# Справка
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/
Простейший вариант
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, так и непосредственно.
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. Это временная общая папка, существующая только когда существует под.
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.
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.
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).
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
Под запустился на 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
На мастер-ноде
# Установить 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 '', то там будет написано:\\
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.
Ноды подключены к внешней сети и также имеют виртуальную кластерную сеть. В кластерной сети работают сервисы, которые балансируют нагрузку на входящие в них поды. У подов своя сеть.
Для внешнего пользователя получить доступ к кластеру можно двумя способами:
- Обращение через 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'' делает то же самое, но, вероятно, менее удобно.
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. Использует селектор в сервисах, чтобы подключаться к подам.
Трафик управляется согласно правилам, описанным в ингресс-ресурсе. Ингресс может быть настроен для следующих функций:
- Сделать сервисы доступными по внешним URL
- Балансировка трафика
- Терминирование SSL/TLS
- 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 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 варианта:
- 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.
# 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 - применимо только к подам, не к узлам.
Т. е.,
- Под с node-affinity-ярлыком key=value будет размещён только на узлах, имеющих совпадающий ярлык.
- Под с pod-affinity-ярлыком key=value будет размещён только на узлах, имеющих поды с совпадающим ярлыком.
Есть 2 режима работы affinity:
- requiredDuringSchedulingIgnoredDuringExecution - требование соответствия ограничению
- 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:
- NoSchedule: не размещать новые поды
- PreferNoSchedule: не размещать новые поды, пока есть альтернатива
- NoExecute - не размещать и выгнать уже существующие
Taints задаются разными способами.\\
На контрольных нодах тейнт задан по умолчанию, чтобы там не размещалась рабочая нагрузка.\\
Команды kubectl drain и kubectl cordon создают тейнт на целевом узле.\\
Тейнт может быть установлен автоматически кластером в определённых условиях, например, из-за:
- нехватки места на ноде
- нехватки памяти
- нехватки доступных ID процессов
- невозможность для планирования
- недоступность по сети
И эти автоматические тейнты могут быть преодолены соответствующими толерами, но этого крайне не рекомендуется делать, т.к. когда эти тейнты ставятся, значит, с узлом проблемы.
# Тейнт может быть задан админом:
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 опции:
- type: к чему применяется - к подам или контейнерам
- defaultRequest: сколько ресурсов запросит приложение по умолчанию
- 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 разных идентификатора:
- Поды (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 не будет.
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:
- 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 (запуск пода под нерутовым пользователем)
Сек. контекст может быть задан на уровне пода или на уровне контейнера. См.
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 |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 =====
В Кубере нет объектов-пользователей. Пользователь - это сертификат + ролевой доступ.
Чтобы создать "пользователя", нужно следующее:
- Сделать пару ключей.
- Сделать запрос на подпись сертификата (CSR)
- Подписать сертификат
- Создать конфиг, где эти ключи будут прописаны для доступа к кластеру
- Создать роль RBAC
- Создать привязку
Демо
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
# На рабочей ноде - статус кублета (т. к. кублет - это самое главное, что есть на рабочей ноде)
systemctl status kubelet
# Проверить сертификат (редко нужно, но если рабочая нода давно работает и кластер не обновляется, сертификат может протухнуть)
sudo openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -text
===== 11.5 Troubleshooting application access =====
Это выяснение проблем с подами, сервисами и ингрессом.\\
Сервис использует селектор для подключения к подам с совпадающим ярлыком. Ингресс подключается к сервису и берёт его селектор для подключения к соответствующим подам. Прежде всего нужно проверить ярлыки всех этих ресурсов.
# Вывести сервисы и конечные точки
kubectl get endpoints
Положим, видно, что у чего-то нет конечной точки. Далее уже нужно разбираться, что к чему, использовать curl и т. д.