k8s
Эта статья на Хабре https://habr.com/ru/articles/783586/
Введение
Зачем?
Представим ситуацию, что мы деплоим по push-модели. В качестве платформы для запуска деплоя у нас используется Gitlab: в нём настроен пайплайн и джобы, разворачивающие приложения в разные окружения в Kubernetes
Какой бы инструмент мы не использовали (kubectl, helm), для манипуляций с ресурсами API нам в любом случае будет необходимо аутентифицироваться при выполнении запросов к Kubernetes. Для этого в запросе надо передать данные для аутентификации, будь то токен или сертификат. И тут возникает несколько вопросов:
Где хранить эти креды?
Хранить креды от кластера можно в Gitlab CI/CD Variables и подставлять в джобу деплоя, но тогда потенциально все пользователи будут деплоить с одними и теми же доступами
Как сделать так, чтобы у каждого пользователя были свои данные для доступа в кластер?
Можно было бы вручную запускать джобы деплоя и в параметры каждый раз подставлять свои аутентификационные данные, но, очевидно, такой подход неудобен и подходит далеко не всем
А что если сделать так, чтобы в качестве провайдера аутентификационных данных для Kubernetes выступал сам Gitlab? Тогда не надо было бы нигде хранить креды, и каждый пользователь мог бы аутентифицироваться в кубере под своей учёткой при запуске деплоя
FATAL post-upgrade error: unable to create/update the DNS service: services “kube-dns” not found
https://github.com/kubernetes/kubeadm/issues/2358
Необходимо скопировать текущий манифест сервиса coredns, изменить название на kube-dns, изменить clusterIP так, чтобы он заканчивался на .10 (например, 10.233.0.10) и применить новый манифест. Адрес .10 может уже занят другим сервисам, необходимо его будет освободить
kubectl -n kube-system get svc coredns -o yaml > kube-dns.yaml
# edit kube-dns.yaml
kubectl -n kube-system apply -f kube-dns.yaml
FATAL post-upgrade error: unable to create deployment: Post "https://127.0.0.1:6443/apis/apps/v1/namespaces/kube-system/deployments?timeout=10s": net/http: request canceled (Client.Timeout exceeded while awaiting headers)
Создание ресурса не укладывается в захардкоженный таймаут в 10s. Это может быть по нескольким причинам
- медленная запись на диск через etcd
- в процессе добавления ресурса срабатывает Mutation Webhook (kubectl get mutatingwebhookconfigurations. admissionregistration.k8s.io). Этот хук означает, что надо где-то изменить отправленный в API манифест. Тот сервис, который должен изменить, по какой-то причине делает это медленно или не делает это вообще - необходимо искать проблему там. Временное решение - отключить тот сервис, чтобы не срабатывал вебхук
Это примерный процесс обновления, который проверен на версиях 1.15-1.18 На более поздних версиях этот процесс должен быть сильно проще, особенно в плане обновления сертификата kubelet на master-нодах
На master-нодах
- Обновить сертификаты компонентов кубера с помощью kubeadm
for cert in apiserver apiserver-kubelet-client front-proxy-client admin.conf controller-manager.conf scheduler.conf; do /usr/local/bin/kubeadm alpha certs renew $cert; done - Обновить сертификаты kubelet
#!/bin/bash set -eu -o pipefail ## kubelet kube_dir="/etc/kubernetes" ### v3 extensions settings cat << EOF > ${kube_dir}/v3_ext keyUsage=keyEncipherment,digitalSignature extendedKeyUsage=clientAuth EOF ### get cert and key cat ${kube_dir}/kubelet.conf | grep certificate-data | awk '{print $2}' | base64 -d > ${kube_dir}/kubelet.crt cat ${kube_dir}/kubelet.conf | grep key-data | awk '{print $2}' | base64 -d > ${kube_dir}/kubelet.key ### generate csr and sign it openssl x509 -x509toreq -in ${kube_dir}/kubelet.crt -out ${kube_dir}/kubelet.csr -signkey ${kube_dir}/kubelet.key openssl x509 -req -in ${kube_dir}/kubelet.csr -CA ${kube_dir}/ssl/ca.crt -CAkey ${kube_dir}/ssl/ca.key -CAcreateserial -out ${kube_dir}/kubelet.crt -days 365 -extfile ${kube_dir}/v3_ext ### insert cert to kubelet.conf cert_data=$(cat ${kube_dir}/kubelet.crt | base64 -w0) sed -i "s/client-certificate-data: .*$/client-certificate-data: $cert_data/" ${kube_dir}/kubelet.conf rm -f ${kube_dir}/kubelet.csr ${kube_dir}/kubelet.crt ${kube_dir}/kubelet.key ${kube_dir}/v3_ext
Это же можно сделать проще
/usr/local/bin/kubeadm alpha kubeconfig user --org system:nodes --client-name system:node:$(hostname) >/etc/kubernetes/kubelet.confДля версии >=1.20 можно также задать cluster-name
/usr/local/bin/kubeadm alpha kubeconfig user --org system:nodes --client-name system:node:$(hostname) --cluster-name cluster.local >/etc/kubernetes/kubelet.conf
- Перезапустить docker и kubelet
systemctl restart docker kubelet
На worker-нодах надо что-то делать только если уже не было включено автоматическое обновление
Включить автоматическое обновление сертификата kubelet
grep -Fr rotateCertificates /etc/kubernetes/kubelet-config.yaml || echo "rotateCertificates: true" >> /etc/kubernetes/kubelet-config.yamlПерезапустить kubelet
systemctl restart kubelet
- Пользователь генерит csr и передаёт администратору кластера
openssl req -new -newkey rsa:2048 -nodes -keyout i.ivanov.key.pem -out i.ivanov.csr -subj "/CN=i.ivanov /O=MY.ORG" - Администратор кластера создаёт манифест CSR в kubernetes, где сертификат закодирован в base64
cat <<EOF | kubectl apply -f - apiVersion: certificates.k8s.io/v1 kind: CertificateSigningRequest metadata: name: i.ivanov spec: request: <CSR_CONTENT> signerName: kubernetes.io/kube-apiserver-client usages: - client auth EOF - Администратор кластера выпускает сертификат, подписанный куберовским CA
kubectl certificate approve i.ivanov - Администратор кластера копирует полученный сертификат и пересылает его пользователю. Подтвержденный csr хранится в течение часа
kubectl get csr/i.ivanov -o json | jq -r '.status.certificate' | base64 -D > i.ivanov.crt.pem - Пользователь добавляет в свой локальный ~/.kube/config настройки для кластера. Администратор передаёт значения certificate-authority-data, server, client-certificate-data (п. 4) пользователю.
- Управление доступом пользователей осуществляется через RBAC
Перезапустить kubelet
Контейнеры продолжают работать, но в момент синхронизации состояния подов с api-server поды могут переходить в состояние 0/1 Running, когда трафик на них перестаёт направляться
Остановить kubelet
Нода переходит в состояние NotReady, никаких событий на подах не происходит - они продолжают пребывать в состоянии 1/1 Running, но трафик на них перестаёт идти (из endpoint’ов сервисов удаляются IP адрес подов, которые находятся на “мёртвой” ноде).
Спустя указанный pod-eviction-timeout (5m по-умолчанию) для kube-controller-manager поды переходят в статус Terminating и начинают запускаться на живых воркерках. Поды будут продолжать находиться в статусе Terminating либо до старта kubelet, либо до удаления ноды из кластера. При этом не выгоняются поды DaemonSet и поды, поднятые kubelet’ом(kube-proxy, nginx-proxy, kube-flannel, nodelocaldns и т.п.)
Немного о RBAC
В kubernetes есть сущность, которая называется RoleBinding. Она определяет, каким субъектам какая роль будет назначена. RoleBinding действует в рамках namespace, в котором она создана. Для разрешения действий во всех namespace без ограничений необходимо создавать ClusterRoleBinding, который является cluster-wide объектом.
Пример RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: edit
namespace: project-a-devel
subjects:
- kind: Group
name: sre
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io
Эта роль:
- Действует в рамках namespace
project-a-devel- (metadata:) - Применяется к пользователям в группе
sre(/O=sre) - (subjects:) - Разрешает действия, описанные в созданной по-умолчанию кластерной роли (ClusterRole)
edit- (roleRef:)
Чем отличаются всякие там NodePorts, LoadBalancer и Ingress? Все они дают возможность внешнему трафику попасть в ваш кластер, но дают эту возможность по-разному. Давайте-ка разберёмся, как они это делают и когда какой тип сервиса лучше использовать.
ClusterIP
ClusterIP — это дефолтный тип сервиса в кубах, он поднимает вам сервис внутри кластера на внутрекластеровом IP. Доступа для внешнего трафика нет, только внутри кластера.
YAML для ClusterIP выглядит как-то так:
apiVersion: v1
kind: Service
metadata:
name: my-internal-service
spec:
selector:
app: my-app
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
“Так, ну и зачем же рассказывать нам о ClusterIP, если он не принимает внешний трафик?” — спросите вы. А всё потому, что попасть на этот сервис можно через Kubernetes proxy!
