https://github.com/sandervanvugt/cka
Мин. требования к нодам:
До установки кластера через kubeadm, нужно поставить
Итак:
git clone https://github.com/sandervanvugt/cka # на всех нодах cd cka # на всех нодах sudo ./setup-container.sh # на всех нодах - установить контейнерный движок sudo ./setup-kubetools.sh # на всех нодах - установить kubetools # Установить кластер sudo kubeadm init # Настроить клиент (описано в тексте после завершения установки кластера) To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # Проверить работоспособность клиента: kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6m29s # Установить сетевой аддон (здесь: calico) kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml # присоединить воркеров к кластеру: sudo kubeadm join 10.1.0.201:6443 --token 654qgh.545455454545454 \ --discovery-token-ca-cert-hash sha256:234234234234234234234234234 # Проверить наличие всех нод kubectl get nodes NAME STATUS ROLES AGE VERSION t-k8s1 Ready control-plane 11m v1.28.2 t-k8s2 Ready <none> 71s v1.28.2 t-k8s3 Ready <none> 67s v1.28.2 # В большинстве случаев достаточно просто kubeadm init, но есть ряд опций, которые могут пригодиться, например, --apiserver-advertise-address: the IP address on which the API server is listening --config: the name of a configuration file used for additional configuration --dry-run: performs a dry-run before actually installing for real --pod-network-cidr: sets the CIDR used for the Pod network --service-cidr: sets the service CIDR to something other than 10.96.0.0/12 # Справка по опциям kubeadm init -h |less # Если kubeadm init или kubeadm join отработали криво, то откатить уже внесённые изменения на хосте можно kubeadm reset # Токен, который выдаётся после инсталляции кластера, временный. Чтобы получить новый для подключения новых нод: sudo kubeadm token create --print-join-command
Настройка автокомплита и алиаса: https://kubernetes.io/docs/reference/kubectl/quick-reference/#bash
Для получения административного доступа к кластеру через клиента, файл /etc/kubernetes/admin.conf
копируется в ~/.kube/config
.
Под рутом для той же цели можно выполнить export KUBECONFIG=/etc/kubernetes/admin.conf
Для более тонкой настройки нужно заводить спец. пользователя и настраивать ему права через управление ролями (RBAC).
Контекст - шаблон конфигурации настроек клиента. При выборе контекста активируется определённая группа параметров клиента.
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
Это стандартный способ развёртывания контейнеров масшабируемым способом. Масштабирование осуществляет механизм ReplicaSet.
Деплой предоставляет возможность RollingUpdate для обновлений без простоя.
# Императивный способ создания деплоя kubectl create deploy # Справка kubectl create deploy -h |less kubectl create deploy firstnginx --image=nginx --replicas=3 deployment.apps/firstnginx created kubectl get all NAME READY STATUS RESTARTS AGE pod/firstnginx-d8679d567-4cxpj 1/1 Running 0 11s pod/firstnginx-d8679d567-9sl2r 1/1 Running 0 11s pod/firstnginx-d8679d567-vwkh7 0/1 ContainerCreating 0 11s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/firstnginx 2/3 3 2 11s NAME DESIRED CURRENT READY AGE replicaset.apps/firstnginx-d8679d567 3 3 2 11s
Запускает по одному экземпляру приложения на каждой ноде. Обычно используется для всяких агентов мониторинга, 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 <none> 27s kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES firstnginx-d8679d567-4cxpj 1/1 Running 0 51m 192.168.185.129 t-k8s3 <none> <none> firstnginx-d8679d567-9sl2r 1/1 Running 0 51m 192.168.100.193 t-k8s2 <none> <none> firstnginx-d8679d567-vwkh7 1/1 Running 0 51m 192.168.185.130 t-k8s3 <none> <none> mydaemon-cw7x8 1/1 Running 0 75s 192.168.185.131 t-k8s3 <none> <none> mydaemon-srrb8 1/1 Running 0 75s 192.168.100.194 t-k8s2 <none> <none>
Подвид деплоя, предназначенный для stateful-приложений или для тех. кому нужно хотя бы одно из перечисленного:
Deployment | StatefulSet |
---|---|
Для Stateless-приложений | Для Stateful-приложений |
Все поды создаются параллельно | Поды создаются последовательно один за другим (pod-0..pod-X) |
Поды удаляются в случайном порядке | Поды удаляются в обратном порядке (pod-X..pod-0) |
Поды имеют случайные имена | Поды имеют чётко определённые имена <name>-<index> |
Все поды используют один и тот же PV (persistent volume) | Каждая реплика использует свой PV. При перезапуске реплики она получает то же имя и подхватывает тот же PV. |
Легко масштабировать | Трудно масштабировать |
Перед использованием стейтфул-сетов нужно настроить общее хранилище. Если удалить ст-сет, то их постоянные тома не удалятся.
Чтобы обеспечить доступ к подам ст-сета, используется безголовый сервис (headless service). В этом случае во внутреннем DNS кубера будет не имя сервиса, а имена всех подов за ним, что позволит обращаться по имени напрямую к подам.
То, что поды будут остановлены после удаления ст-сета, не гарантировано, поэтому рекомендуется сначала масштабировать реплики в ст-сете до нуля.
Ст-сет похож на деплой, только сервис у него безголовый (clusterIP: None
) и присутствует volumeClaimTemplates
, определяющий PVC для подов в ст-сете.
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web # Headless service clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: k8s.gcr.io/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteMany" ] resources: requests: storage: 1Gi
Это нужно только для тестирования, поиска проблем или анализа, потому что
Так что в обычной ситуации нужно использовать деплой, д-сет и ст-сет.
# Справка kubectl run -h |less # Запустить под, который закончит работу через 1 ч. kubectl run sleepy --image=busybox -- sleep 3600
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"
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
Обычно под содержит один контейнер, но иногда бывают случаи, когда в поде нужен дополнительный контейнер для представления или модификации данных основного контейнера. Виды таких контейнеров-спутников:
Тома, подключенные к поду, используются как общее хранилище. Основной контейнер пишет туда, а сайдкар читает это.
Том может быть подключен как через 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 может использоваться 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
Могут быть созданы вручную или автоматически (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
Позволяет поду подключиться к 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 ГБ.
В 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 <none> <none>
Под запустился на t-k8s3, так что там в каталоге /mydata
должен теперь лежать hello.html
.
Так как PV локальный, то на других нодах hello.html
не будет, равно как и каталога /mydata
.
В целом путь такой: PV → PVC (cсылается на имя PV) → Pod volume (cсслыется на имя PVС) → Pod volumeMount (cсслыется на имя volume).
Позволяет автоматически предоставлять хранилище в пользование. Даже если 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 Provisioner (provider) работает с storageClass, чтобы автоматически предоставлять место на хранилище. Он запускается как под в кластере, предоставляющий функции контроля доступа. Когда провайдер настроен, PV больше не нужно настраивать руками.
Чтобы провайдер работал, ему нужен доступ к API, ведь он создаёт ресурсы, и ему нужны разрешения на это.
Roles и RoleBindings используются для создания таких разрешений. ServiceAccount создаётся для подключения пода к нужной RoleBinding.
На мастер-ноде
sudo apt install nfs-server -y sudo mkdir /nfsexport sudo echo "/nfsexport *(rw,no_root_squash)" > /etc/exports sudo systemctl restart nfs-server
На остальных нодах
sudo apt install nfs-client -y # Проверить, видят ли они NFS-шару showmount -e <control node IP>
На мастер-ноде
# Установить helm: скачать релиз и скопировать бинарник в /usr/local/bin tar xvf helm.tar.gz sudo cp linux-amd64/helm /usr/local/bin # Добавить репозиторий helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner # Установить helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=192.168.1.21 --set nfs.path=/nfsexport
Провайдер появляется в списке подов.
Дальше создаётся PVC. storageClassName должен соответствовать имени storageClass (список - kubectl get storageclass
)
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc-test spec: storageClassName: nfs-client # SAME NAME AS THE STORAGECLASS accessModes: - ReadWriteMany # must be the same as PersistentVolume resources: requests: storage: 50Mi
После применения создаётся PVC с соответствующим storageClassName.
Чтобы постоянно не указывать в манифесте PVC storageClassName, нужно сделать какой-то storageClass по умолчанию.
Если storageClass по умолчанию не указан и применён манифест PVC, где нет storageClassName, то kubectl get pvc покажет, что PVC находится в статусе pending, и если посмотреть kubectl describe pvc <pending pvc name>
, то там будет написано:
No persistent volumes available for this claim and no storage class is set
Применяем патч, указывающий, что этот storageClass нужно сделать по умолчанию:
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/
После применения патча PVC без storageClassName будут успешно привязаны.
kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE another-nfs-pvc-test Bound pvc-68404b95-b113-4f49-bd19-75b959f651d6 50Mi RWX nfs-client 14m
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
В k8s сетевое взаимодействие работает на несольких уровнях:
Ноды подключены к внешней сети и также имеют виртуальную кластерную сеть. В кластерной сети работают сервисы, которые балансируют нагрузку на входящие в них поды. У подов своя сеть. Для внешнего пользователя получить доступ к кластеру можно двумя способами:
Сетевой плагин нужен для обеспечения связи между подами. Его необходимо ставить отдельно, т. к. он не входит в стандартный k8s. Существует несколько реализаций сетевого плагина, разные плагины поддерживают разные набор функций. Часто используется Calico, т. к. у него есть NetworkPolicy.
Сервисы обеспечивают доступ к подам. Если за сервисом несколько подов то сервис будет балансировать трафик между подами. Есть несколько типов сервисов:
Команда 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).
Ингресс предоставляет внешний доступ к сервисам кластера. Работает с внешним DNS, чтобы доступ был по URL. Состоит из 2 частей: балансировщик, доступный из внешней сети и API-ресурс, контактирующий с сервисами, чтобы обнаружить поды за ними. Ingress надо ставить отдельно, его нет в стандартной установке Кубера. Ингресс работает только с HTTP/HTTPS. Использует селектор в сервисах, чтобы подключаться к подам.
Трафик управляется согласно правилам, описанным в ингресс-ресурсе. Ингресс может быть настроен для следующих функций:
helm upgrade --install ingress-nginx ingress-nginx --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace kubectl get pods -n ingress-nginx kubectl create deploy nginxsvc --image=nginx --port=80 kubectl expose deploy nginxsvc # Создать правило ингресса: kubectl create ingress nginxsvc --class=nginx --rule=nginxsvc.ru/*=nginxsvc:80
Если посмотреть внутрь созданного ингресса (kubectl describe ingress nginxsvc), то там не будет селектора - ингресс работает через правило.
Селектор играет роль в сервисе, поэтому если выдаются 500-е ошибки, надо проверять селектор сервиса.
Узнать порт доступа к ингрессу извне:
kubectl get service -n ingress-nginx ingress-nginx-controller NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.109.33.100 <pending> 80:32452/TCP,443:30462/TCP 12m
Теперь после заведения DNS-записи можно обращаться к http://nginxsvc.ru:32452 или https://nginxsvc.ru:30462
# Справка с примерами 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.
При создании ингресс-правил опция --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 ...
Нужен для тестирования доступа к приложению без возни с сервисами и ингрессом.
# Редирект с 1234 на 80 kubectl port-forward mypod 1234:80
По умолчанию запускается на переднем плане, поэтому чтобы освободить консоль, можно нажать Ctrl+Z или сразу запускать команду с & в конце. Например
kubectl port-forward mypod 1234:80 & curl localhost:1234
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
# Информация по кублету 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.
Это поды, запущенные непосредственно процессом 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 ...
kubectl cordon # больше не принимать нагрузку kubectl drain # больше не принимать нагрузку и убрать все поды --ignore-daemonsets # и демонсеты тоже --delete-emptydir-data # удалить данные из томов emptyDir kubectl uncordon # вернуть ноду в список доступных для распределения нагрузки
cordon/drain ставят taint на ноду, что и даёт эффект того, что на неё нельзя распределить нагрузку.
Поды автоматически после возвращения ноды не распределяются.
На рабочей ноде это кублет и контейнерный движок, управляемый 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), то он сам перезапускаться не будет.
Сервер метрик нужен для мониторинга нод и состояния подов. Его нужно установить отдельно.
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 работает как статический под на мастер-ноде, его потеря значит потеря всей конфигурации кластера. Это делается под рутом, используя 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
Очень желательно бэкап держать в нескольких местах для надёжности.
# Можно вытереть пару деплоев, чтобы был виден результат восстановления 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
Пропускать минорные версии нельзя (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
# 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)
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
2 варианта:
Для отказоустойчивого кластера нужен балансировщик, например, 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.
Планировщик отвечает за размещение подов на рабочих узлах. Узлы отбираются по критериям, которые можно задать:
Планировщик перед размещением находит узлы-кандидаты и оценивает их, затем подбирает узел с наивысшей оценкой. Дальше планировщик начинает привязку, уведомляет API-сервер и передаёт задачу кублету, который уже инструктирует контейнерный движок (CRI) скачать образ и запустить контейнер.
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, где можно задать конкретное имя узла, на котором всегда будет запускаться этот под. Но так не рекомендуется делать - если узел выйдет из строя, то под не запустится больше нигде.
Более продвинутые правила размещения подов.
Т. е.,
Есть 2 режима работы affinity:
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
Taint применяется к узлам. Это метка, которая препятствует запуску любых подов, если у них нет соответствующего toleration.
Toleration применяется к подам. Это метка, которая позволяет (но не предписывает!) поду запуститься на узле с соответствующим taint.
Если affinity применяется для того, чтобы привязать поды к конкретным узлам, то taint - чтобы исключить их запуск на узлах.
Taints и tolerations гарантируют, что поды не запускаются на неподходящих узлах, таким образом гарантируя, что на выделенных узлах запускаются только поды для конкретных задач.
Есть 3 типа taints:
Taints задаются разными способами.
На контрольных нодах тейнт задан по умолчанию, чтобы там не размещалась рабочая нагрузка.
Команды kubectl drain и kubectl cordon создают тейнт на целевом узле.
Тейнт может быть установлен автоматически кластером в определённых условиях, например, из-за:
И эти автоматические тейнты могут быть преодолены соответствующими толерами, но этого крайне не рекомендуется делать, т.к. когда эти тейнты ставятся, значит, с узлом проблемы.
# Тейнт может быть задан админом: 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 игнорируется.
LimitRange - API-объект, ограничивающий использование ресурсов подом или контейнером в пространстве имён. 3 опции:
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
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
Кубер запускает поды 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
Изначально между подами нет сетевых ограничений и они могут свободно общаться даже если находятся в разных пространствах имён. Чтобы регулировать трафик, существуют сетевые политики. Чтобы политики работали, они должны поддерживаться сетевым плагином. Например, плагин weave не поддерживает сетевых политик.
Если политика есть и правило не совпадает, то трафик запрещается.
Если политики нет, трафик разрешается.
В сетевой политике есть 3 разных идентификатора:
Когда используется сетевая политика с 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" значит, что исходящий трафик не ограничен.
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 # не работает (что и требуется)
Прямого доступа к ETCD нет, доступ осуществляется через kube-apiserver. Права доступа описываются в Roles и clusterRoles.
Есть 2 способа доступа к API:
.kube/config
.Итак, kubectl/pod обращаются к API и дальше им нужен доступ к ETCD, для этого надо подключиться к Role или clusterRole для определения полномочий. roleBinding и clusterRoleBinding это привязки к Role и clusterRole для kubectl/pod (типа PVC для PV).
Определяет права и настройки доступа для подов и контейнеров. Включает в себя следующее:
Сек. контекст может быть задан на уровне пода или на уровне контейнера. См.
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
В Кубере нет внутренних пользователей. Они берутся извне:
Сервисные учётки используются для авторизации подов для получения доступа к специфическим ресурсам.
# Сервисные учётки, существующие в текущем контексте kubectl get sa # Помимо стандартной, там есть ещё учётка NFS Storage Provisioner, потому что её нужно создавать PVs, а для этого нужен доступ к API. #Все сервис-учётки: kubectl get sa -A # Найти запись об используемой сервис-учётке можно в свойствах пода kubectl describe po <podname> |grep -i serviceaccount
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
Обычная роль действует в рамках namespace, кластерная роль распространяется на весь кластер.
Работает кластерная роль точно так же, как и обычная. Чтобы привязать пользователя или сервис-учётку к кластерной роли, нужен clusterRoleBinding.
# Список кластерных ролей kubectl get clusterrole # Опции (хорошая шпаргалка по синтаксису) kubectl get clusterrole edit -o yaml |less # Список привязок kubectl get clusterrolebindings
В Кубере нет объектов-пользователей. Пользователь - это сертификат + ролевой доступ.
Чтобы создать «пользователя», нужно следующее:
Демо
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
Самое элементарное - 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
, иначе работать с неподписанными сертификатами не будет. Но это применимо только в тестовой среде!
Ресурсы создаются сначала в ETCD. Уже с этого момента можно использовать команды
kubectl describe kubectl events # Дальше под стартует на запланированных узлах, перед стартом нужно скачать образ. Вывести список доступных образов на конкретной ноде можно sudo crictl images # После запуска уже можно смотреть в логи kubectl logs [-f] # и лезть в консоль.
kubectl get показывает базовый статус.
Что именно поломалось, надо выяснять у kubectl describe
. Если это первичный контейнер пода, то см. логи. Если под работает, но не так, как нужно, тогда можно зайти в консоль.
# Основная информация о состоянии кластера kubectl cluster-info # ОЧЕНЬ подробная информация из всех кластерных логов (нужно парсить, чтобы что-то выцепить) kubectl cluster-info dump kubectl get nodes kubectl get po -n kube-system # Информация об узле, особое внимание на раздел Conditions, Taints, Allocated resources kubectl describe node <nodename> # На рабочей ноде - статус кублета (т. к. кублет - это самое главное, что есть на рабочей ноде) systemctl status kubelet # Проверить сертификат (редко нужно, но если рабочая нода давно работает и кластер не обновляется, сертификат может протухнуть) sudo openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -text
Это выяснение проблем с подами, сервисами и ингрессом.
Сервис использует селектор для подключения к подам с совпадающим ярлыком. Ингресс подключается к сервису и берёт его селектор для подключения к соответствующим подам. Прежде всего нужно проверить ярлыки всех этих ресурсов.
# Вывести сервисы и конечные точки
kubectl get endpoints
Положим, видно, что у чего-то нет конечной точки. Далее уже нужно разбираться, что к чему, использовать curl и т. д.