Single node Kubernetes setup - Ubuntu 18.04
sudo apt-get -y purge cloud-init && sudo rm -rf /etc/cloud sudo apt-get install -y apt-transport-https \ ca-certificates curl \ gnupg-agent \ software-properties-common sudo apt-add-repository universe echo 'deb http://apt.kubernetes.io/ kubernetes-xenial main' | sudo tee /etc/apt/sources.list.d/kubernetes.list ######### curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add gpg_key_url="https://packages.cloud.google.com/apt/doc/apt-key.gpg" gpg_keyring_path="/etc/apt/trusted.gpg.d/kubernetes.gpg" curl -fsSL "${gpg_key_url}" | gpg --dearmor | sudo tee -a ${gpg_keyring_path}
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF
sudo modprobe overlay sudo modprobe br_netfilter
# Setup required sysctl params, these persist across reboots. cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF
# Apply sysctl params without reboot sudo sysctl --system
sudo apt-get update #sudo apt-get install -y docker.io kubeadm kubelet sudo apt-get install -y containerd kubeadm kubelet sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml cat << EOF | sudo tee /etc/systemd/system/kubelet.service.d/12-after-docker.conf [Unit] After=containerd.service EOF sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml sudo service containerd restart # Kubernetes Cluster Init sudo kubeadm init --cri-socket /run/containerd/containerd.sock --pod-network-cidr=10.244.0.0/16 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
#https://github.com/cloudnativelabs/kube-router/blob/master/docs/kubeadm.md #kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml #kubectl -n kube-system patch deployment coredns --type=json -p='[{"op": "replace", "path": "/spec/replicas", "value":1}]' kubectl taint nodes --all node-role.kubernetes.io/master-
Настройка манифестов компонентво кубера на слабых маишинках
Я запускаю свой кластер в контейнере на сервере Proxmox, на довольно слабой машинке, поэтому при старте компоненты кубера начинают отвечать очень нескоро.
Чтобы немного облегчить им жизнь и сделать поведение компонентов k8s более предсказуемым нужно увеличить таймауты livenessProbe, readinessProbe и startupProbe - в результате у сервисов будет больше времени чтобы прийти в норму при запуске/перезапуске и кластер будет вести себя стабильнее, особенно если в нем много полезной нагрузки.
Для этого - редактируем манифесты в директории /etc/kubernetes/manifests/ и подкручиваем там значения до, например, таких:
livenessProbe: failureThreshold: 300 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 30 readinessProbe: failureThreshold: 300 periodSeconds: 15 timeoutSeconds: 30 startupProbe: failureThreshold: 300 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 30
И перезапускаем kubelet:
sudo service kubelet restart
Изменение редактора kubectl edit
sudo awk -v line='export KUBE_EDITOR="/bin/nano"' 'FNR==NR && line==$0{f=1; exit} END{if (!f) print line >> FILENAME}' /etc/bash.bashrc
Автодополнение kubectl
sudo awk -v line='source <(kubectl completion bash)' 'FNR==NR && line==$0{f=1; exit} END{if (!f) print line >> FILENAME}' /etc/bash.bashrc
/var/lib/kubelet/config.yaml
Failed to initialize CSINodeInfo
Если нода не переходит в состояние Ready, а при выполнении
kubectl describe nodes
Видна ошибка:
Failed to initialize CSINodeInfo: error updating CSINode annotation: timed out waiting for the condition; caused by: the server could not find the requested resource
то в файлике /var/lib/kubelet/config.yaml нужно добавить:
featureGates: CSIMigration: false
cgroupDriver
https://kubernetes.io/docs/setup/production-environment/container-runtimes/
Также в файлике /var/lib/kubelet/config.yaml нужно включить такой же cgroupDriver, что и для docker (в файлике /etc/docker/daemon.json). По дефолту стоит cgroupfs.
И вообще - если в системе есть systemd, то нужно использовать cgroupDriver. Если будет включен cgroupfs, то ресурсами будут управлять два менеджера одновременно, что негативно сказыватся на стабильности.
То есть просто в конце файлика с нулевым отступом можно дописать:
cgroupDriver: systemd
Kubelet Log level
/etc/systemd/system/multi-user.target.wants/kubelet.service
ExecStart=/usr/bin/kubelet --v=2
sudo systemctl daemon-reload sudo systemctl restart kubelet
CoreDNS
Изменяем дефолтный DNS-сервер для forwarding'a запросов
Конфигурация CoreDNS хранится в ConfigMap coredns
kubectl edit cm coredns -n kube-system
Редактируем параметр forward. Вместо /etc/resolv.conf пописываем адрес нужного DNS-сервера
forward . 192.168.1.100
Kubernetes monitoring
Чтобы работали команды
kubectl top nodes
и
kubectl top pods
Нужно установить сервер метрик.
Установка сервера метрик kubernetes
https://github.com/kubernetes-sigs/metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
В результате в неймспейсе kube-system появится deployment metrics-server и развернется pod metrics-server-….
В нашем кластере отключен ssl, но он включен по-дефолту в metrics-server. Поэтому выполняем:
kubectl edit deploy -n kube-system metrics-server
и в spec.template.spec.containers.args добавляем:
- --kubelet-insecure-tls
Без этого параметра metrics-server не запустится, а в логах будет примерно такое:
[unable to fully scrape metrics from node kub-worker01: unable to fetch metrics from node kub-sbl-apps-dev-worker01: Get "https://192.168.44.11:10250/stats/summary?only_cpu_and_memory=true": x509: cannot validate certificate for 192.168.44.11 because it doesn't contain any IP SANs
Все. Через некоторое время команды kubectl top начнут выдавать осмысленную информацию.
Если этого не происходит, а в логах
kubectl logs -n kube-system metrics-server...
Видно такое:
reststorage.go:160] unable to fetch pod metrics for ...
То можно попробывать добавить в в spec.template.spec.containers.args такое:
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
Helm
С третьей версией Helm ничего в кластер ставить не нужно. Для использования достаточно бинарника и сконфигурированного context для доступа к кластеру.
Обновление kubernetes
смотрим какие версии kubeadm нам доступны
sudo apt-get update apt-cache madison kubeadm
Разрешаем обновление kubernetes-cni и kubeadm:
sudo apt-mark unhold kubernetes-cni kubeadm
Ставим нужную версию kubeadm:
sudo apt-get install kubeadm=1.20.9-00
Проверяем возможность апгрейда:
sudo kubeadm upgrade plan
Если у нас хост с containerd (без docker), то нужно проверить, что параметр kubeadm.alpha.kubernetes.io/cri-socket указывает не на docker-shim, а на сокет containerd. У меня так:
kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
если этого не сделать - будет ошибка, поскольку kubeadm возьмет эту запись и будет пытаться работать через docker, которого нет:
error execution phase preflight: docker is required for container runtime: exec: "docker": executable file not found in $PATH
Обновляем:
sudo kubeadm upgrade apply v1.20.9
Разрешаем обновление kubelet и kubectl
sudo apt-mark unhold kubelet kubectl
И обновляем их до нужной версии:
sudo apt-get install -y kubelet=1.20.9-00 kubectl=1.20.9-00
Морозим обратно версии пакетов:
sudo apt-mark hold kube*
Апгрейдим всю систему:
sudo apt-get update && sudo apt-get upgrade
Доступ к подам снаружи
Для доступа к сервисам, которые предоставляют поды, нужно установить и настроить:
- LoadBalancer MetalLB для получения сервисами IP-адресов локальной (или нелокальной) сети.
- Ingress Controller для маршрутизации HTTP/HTTPS запросов.
- Cert Manager для управления сертификатами (в том числе и Let's Encrypt).
LoadBalancer - MetalLB
https://habr.com/ru/company/southbridge/blog/443110/
https://metallb.universe.tf/installation/
helm repo add metallb https://metallb.github.io/metallb helm repo update kubectl create ns metallb-system helm upgrade --install -n metallb-system metallb metallb/metallb \ --set configInline.address-pools[0].name="default" \ --set configInline.address-pools[0].protocol="layer2" \ --set configInline.address-pools[0].addresses[0]="192.168.77.160-192.168.77.189"
Апгрейдим metallb
helm repo update helm upgrade -n metallb-system metallb metallb/metallb --reuse-values
Ingress-controller
https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/#
Либо тоже самое можно сделать с помощью helm-чарта:
kubectl create ns ingress helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update
#helm install stable/nginx-ingress --name http-https --namespace ingress helm upgrade --install nginx -n ingress ingress-nginx/ingress-nginx \ --set controller.service.type=LoadBalancer,controller.service.externalTrafficPolicy=Cluster,controller.service.loadBalancerIP=192.168.77.160 \ --set controller.addHeaders."X-XSS-Protection"="1\;mode=block" \ --set controller.addHeaders."Content-Security-Policy"="default-src 'self'; \ script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com https://www.googletagmanager.com 'nonce-HM305BL1jG4mB1xm' https://mc.yandex.ru; \ img-src 'self' https://www.google-analytics.com https://*.yandex.ru; \ style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \ font-src 'self' https://themes.googleusercontent.com;" \ --set controller.addHeaders."Strict-Transport-Security"="max-age=31536000;includeSubdomains;preload" \ --set controller.addHeaders."X-Frame-Options"="SAMEORIGIN" \ --set controller.addHeaders."X-Content-Type-Options"="nosniff"
Ну или без драконовских запретов (заголовки Content-Security-Policy влияют на все ресурсы за этим ingress-controller и что-то может просто не заработать):
helm upgrade --install nginx -n ingress ingress-nginx/ingress-nginx \ --set controller.service.type=LoadBalancer \ --set controller.service.externalTrafficPolicy=Cluster \ --set controller.service.loadBalancerIP=192.168.77.160 \ --set controller.addHeaders."X-XSS-Protection"="1\;mode=block" \ --set controller.addHeaders."Strict-Transport-Security"="max-age=31536000;includeSubdomains;preload" \ --set controller.addHeaders."X-Frame-Options"="SAMEORIGIN" \ --set controller.addHeaders."X-Content-Type-Options"="nosniff"
В результате - в неймспейсе ingress появится сервис nginx-ingress-nginx-controller, у которого будет тип LoadBalancer и который получит указанный IP-адрес в локальной сети из диапазона, сконфигурированного для metallb.
Теперь можно создавать ingress'ы, которые будут смотреть на сервисы внутри кластера и предоставлять к ним доступ. Пользователи будут посылать запросы на ingress-controller, а он, в свою очередь, - пересылать их на ingress'ы.
Мониторинг nginx ingess
В кластере нужно развернуть CRD и оператор prometheus: https://wiki.autosys.tk/devops/prometheus_federation#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%B0_prometheus
nginx-ingress-monitoring-values.yaml
controller: metrics: port: 10254 enabled: true service: annotations: prometheus.io/scrape: "true" prometheus.io/port: "10254" servicePort: 10254 type: ClusterIP serviceMonitor: enabled: true additionalLabels: jobLabel: nginx-ingress namespace: "ingress" namespaceSelector: matchNames: - ingress scrapeInterval: 30s prometheusRule: enabled: true namespace: ingress rules: - alert: NGINXConfigFailed expr: count(nginx_ingress_controller_config_last_reload_successful == 0) > 0 for: 1s labels: severity: critical annotations: description: bad ingress config - nginx config test failed summary: uninstall the latest ingress changes to allow config reloads to resume - alert: NGINXCertificateExpiry expr: (avg(nginx_ingress_controller_ssl_expire_time_seconds) by (host) - time()) < 604800 for: 1s labels: severity: critical annotations: description: ssl certificate(s) will expire in less then a week summary: renew expiring certificates to avoid downtime - alert: NGINXTooMany500s expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"5.+"} ) / sum(nginx_ingress_controller_requests) ) > 5 for: 1m labels: severity: warning annotations: description: Too many 5XXs summary: More than 5% of all requests returned 5XX, this requires your attention - alert: NGINXTooMany400s expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"4.+"} ) / sum(nginx_ingress_controller_requests) ) > 5 for: 1m labels: severity: warning annotations: description: Too many 4XXs summary: More than 5% of all requests returned 4XX, this requires your attention
helm upgrade --reuse-values -n ingress nginx ingress-nginx/ingress-nginx -f ./nginx-ingress-monitoring-values.yaml
Cert manager
https://cert-manager.io/docs/installation/helm/
Устанавливаем:
kubectl create namespace cert-manager helm repo add jetstack https://charts.jetstack.io helm repo update helm upgrade --install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --set installCRDs=true \ --version v1.14.4 \ --set prometheus.enabled=false
https://docs.cert-manager.io/en/latest/tasks/issuers/index.html
Создаем издателя Let's Encrypt (ACME Issuer):
kubectl apply -f - << EOF apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: # You must replace this email address with your own. # Let's Encrypt will use this to contact you about expiring # certificates, and issues related to your account. email: mike@autosys.tk server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: # Secret resource used to store the account's private key. name: cert-issuer-account-key # Add a single challenge solver, HTTP01 using nginx solvers: - http01: ingress: class: nginx EOF
Смотрим на его состояние:
kubectl describe clusterissuer letsencrypt
Должно быть так:
Status: ... Conditions: ... Status: True Type: Ready
Апгрейдим cert-manager
helm repo update helm upgrade --namespace cert-manager cert-manager jetstack/cert-manager --reuse-values
Ingress, Cert-Manager и сертификаты Let's Encrypt
https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html
https://docs.bitnami.com/kubernetes/how-to/secure-kubernetes-services-with-ingress-tls-letsencrypt/#step-3-configure-tls-with-let-s-encrypt-certificates-and-cert-manager
https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/#
https://cert-manager.io/docs/installation/helm/
Итак, у нас установлен MetalLB, Ingress Controller и CertManager.
В результате, сервис Ingress Controller должен получить адрес в локальной (или нелокальной сети) из диапазона, прописанного в конфигурации MetalLB:
# kubectl get svc -n ingress NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE http-https-nginx-ingress-controller LoadBalancer 10.100.109.113 192.168.77.160 80:31833/TCP,443:32690/TCP 5h1m http-https-nginx-ingress-default-backend ClusterIP 10.107.152.17 <none> 80/TCP 5h1m
Для примера буду делат доступ к AWX, который установлен в моем кластере Kubernetes.
В неймспейсе, куда развернут AWX (в моем случае это awx) делаем ingress, который будет направлять запросы в соотвествующий сервис, где работает web-морда AWX:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: awx-web namespace: awx spec: rules: - host: awx.autosys.tk http: paths: - backend: serviceName: awx-web-svc servicePort: 80 path: / tls: - hosts: - awx.autosys.tk secretName: awx-autosys-tk-tls
В результате, HTTP-запросы на IP-адрес Ingress-контроллера будут перенаправлены на сервис web-морды AWX.
Теперь нужно сконфигурировать ACME Issuer - это сущность, которая будет запрашивать сертификаты по протоколу ACME у серверов Let's Encrypt:
https://cert-manager.io/docs/configuration/acme/
kubectl apply -f - << EOF apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: # You must replace this email address with your own. # Let's Encrypt will use this to contact you about expiring # certificates, and issues related to your account. email: mike@autosys.tk server: https://acme-staging-v02.api.letsencrypt.org/directory privateKeySecretRef: # Secret resource that will be used to store the account's private key. name: letsencrypt-issuer-account-key # Add a single challenge solver, HTTP01 using nginx solvers: - http01: ingress: class: nginx EOF
После того, как создан Issuer (в данном случае - ClusterIssuer с именем letsencrypt) можно добавить в манифест ингресса информацию о нем. Редактируем ресурс Ingress и приводим секцию annotations к виду:
kind: Ingress metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt kubernetes.io/ingress.class: nginx name: awx-web namespace: awx
Все. Теперь можно увидеть, что появился сертификат:
kubectl get certificate -A
Если сертификат долго не готов, то нужно смотреть логи пода cert-manager:
kubectl get po -n cert-manager kubectl logs cert-manager-55c44f98f-g9vrb -n cert-manager -f
У меня, например, возникали проблемы с резолвингом доменнного имени, для которого получаем сертификат в поде cert-manager, хотя из интернета имя резолвилось корректно и маршрутизация трафика работала нормально, но cert-manager, перед тем как запросить проверку у ACME-сервера, пытается сам убедиться, что проверка проходит, но так как имя в локалке не резолвилось корректно, то проверка не прошла.
Rancher
Rancher - это web-консоль для управления кластеами kubernetes. Устанавливать осторожно, поскольку она ставит с кластер много всякого и полностью удалить ее может быть проблематично.
https://rancher.com/docs/rancher/v2.x/en/installation/ha/helm-rancher/
Если у нас уже установлены MetalLB, Ingress Controller и Cert-Manager, то можно ставить так:
helm install rancher-latest/rancher --name rancher --namespace cattle-system --set hostname=rancher.autosys.tk --set ingress.tls.source=letsEncrypt --set letsEncrypt.email=mike@autosys.tk
В моем случае, helm ругнулся:
Error: validation failed: unable to recognize "": no matches for kind "Issuer" in version "certmanager.k8s.io/v1alpha1"
После этого я скачал чарт:
helm fetch rancher-latest/rancher
Распаковал его:
tar -xvf ./rancher-2.3.0.tgz
И просто удалил файл, который создает Issuer, так как у меня в кластере уже есть ClusterIssuer.
rm ./rancher/templates/issuer-letsEncrypt.yaml
Затем запаковал чарт обратно и установил его:
mv ./rancher-2.3.0.tgz ./rancher-2.3.0.tgz.orig helm package rancher helm install ./rancher-2.3.0.tgz --name rancher --namespace cattle-system --set hostname=rancher.autosys.tk --set ingress.tls.source=letsEncrypt --set letsEncrypt.email=mike@autosys.tk
В итоге rancher установился нормально!
Затем я отредактировал ingress, заменив issuer:
certmanager.k8s.io/issuer: rancher
на уже существующий в кластере:
cert-manager.io/cluster-issuer: letsencrypt kubernetes.io/ingress.class: nginx
Залогиниться в первый раз можно с помощью логина/пароля admin/admin.
awx on single nodes kubernetes
Для базы данных я использую внешний сервер postgres, то есть StorageClass и PersistentVolume для базы мне не нужны.
После запуска playbook'a будет создан StatefullSet. Однако, если нода достаточно слабая, то под не запустится, потому что не хватит ресурсов. Нужно удалить все объекты resources с резервированием и лимитами ресурсов:
kubectl edit sts awx -n awx
Также, нода будет иметь taint:
taints: - effect: NoSchedule key: node.kubernetes.io/disk-pressure timeAdded: "2019-10-01T12:28:30Z"
Чтобы под запустился нужно добавить в конфигурацию StatefullSet:
kubectl edit sts awx -n awx
соответствующий toleration (в spec:template:spec:. Например - перед volumes):
tolerations: - effect: NoSchedule operator: Exists
Discussion