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 и т. д.