Новые проекты пошли стабильным потоком. Каждый проект требует себе несколько кластеров: под тесты и продуктив. Делать все руками — не вариант, поэтому стоит автоматизировать подготовку новых кластеров.
Новые тестовые кластеры требуют типичных простых настроек. Нужно подготовить инвентарь и проверить его работу. Требования к инвентарю:
- подготовка работы кластера из 5 нод: 1 мастер и 4 рабочие ноды;
- в качестве CRI — containerd;
- запуск etcd производить на мастере.
Для развертывания инфраструктуры в Яндекс Облаке используем терраформ, конфигурация манифестов terraform лежит в текущем репозитории, папка terrform.
Запуск для удобства через баш скрипт start.sh.
В итоге получены 5 ВМ, адресация ниже:
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
Outputs:
external_ip_address_vm_1 = "158.160.43.129"
external_ip_address_vm_2 = "158.160.43.224"
external_ip_address_vm_3 = "130.193.51.14"
external_ip_address_vm_4 = "158.160.39.36"
external_ip_address_vm_5 = "84.201.156.234"
internal_ip_address_vm_1 = "192.168.250.10"
internal_ip_address_vm_2 = "192.168.250.25"
internal_ip_address_vm_3 = "192.168.250.19"
internal_ip_address_vm_4 = "192.168.250.37"
internal_ip_address_vm_5 = "192.168.250.9"
Подготовляем инвентарь для kubespray на мастер машине:
dgolodnikov@cp1:~/REPO/$ git clone https://github.com/kubernetes-sigs/kubespray
dgolodnikov@cp1:~/REPO/$ cd kubespray
dgolodnikov@cp1:~/REPO/kubespray$ cat requirements.txt
ansible==5.7.1
ansible-core==2.12.5
cryptography==3.4.8
jinja2==2.11.3
netaddr==0.7.19
pbr==5.4.4
jmespath==0.9.5
ruamel.yaml==0.16.10
ruamel.yaml.clib==0.2.7
MarkupSafe==1.1.1
dgolodnikov@cp1:~/REPO/kubespray$ sudo pip3 install -r requirements.txt
Копируем инвентарь в отдельную папку, назовем firstcluster. С помощью билдера создаем hosts.yaml.
dgolodnikov@cp1:~/REPO/kubespray$ cp -rfp inventory/sample inventory/mycluster
dgolodnikov@cp1:~/REPO/kubespray$ declare -a IPS=(192.168.250.10 192.168.250.25 192.168.250.19 192.168.250.37 192.168.250.9)
dgolodnikov@cp1:~/REPO/kubespray$ CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
Корректируем полученный файл до следующего вида:
dgolodnikov@cp1:~/kubespray/inventory/firstcluster$ cat hosts.yaml
all:
hosts:
cp1:
ansible_host: 192.168.250.10
ip: 192.168.250.10
access_ip: 192.168.250.10
ansible_user: dgolodnikov
node1:
ansible_host: 192.168.250.25
ip: 192.168.250.25
access_ip: 192.168.250.25
ansible_user: dgolodnikov
node2:
ansible_host: 192.168.250.19
ip: 192.168.250.19
access_ip: 192.168.250.19
ansible_user: dgolodnikov
node3:
ansible_host: 192.168.250.37
ip: 192.168.250.37
access_ip: 192.168.250.37
ansible_user: dgolodnikov
node4:
ansible_host: 192.168.250.9
ip: 192.168.250.9
access_ip: 192.168.250.9
ansible_user: dgolodnikov
children:
kube_control_plane:
hosts:
cp1:
kube_node:
hosts:
node1:
node2:
node3:
node4:
etcd:
hosts:
cp1:
k8s_cluster:
children:
kube_control_plane:
kube_node:
calico_rr:
hosts: {}Для доступа к кластеру снаружи добавлем строку supplementary_addresses_in_ssl_keys: [158.160.43.129] в inventory/firstcluster/group_vars/k8s_cluster/k8s-cluster.yml в которой прописываем внешний адрес кластера. Файл конфигурации в полном обьеме выложен здесь.
Запускаем ансибл для установка кластера с нашим инвентарем. Также запрещаем host cheking в аргументах. Предварительно копируем приватный ключ на мастер ноду, т.к. на всех созданных ВМ залит публичный ключ:
dgolodnikov@cp1:~/.kube$ ansible-playbook -i inventory/firstcluster/hosts.yaml --ssh-common-args='-o StrictHostKeyChecking=no' cluster.yml -b -v
Копируем конфигурацию кластера для kubectl:
dgolodnikov@cp1:~$ sudo cp /etc/kubernetes/kubeadm-config.yaml ~/.kube/config
Проверяем на мастер ноды, что кластер создан:
dgolodnikov@cp1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
cp1 Ready control-plane 8h v1.25.4
node1 Ready <none> 8h v1.25.4
node2 Ready <none> 8h v1.25.4
node3 Ready <none> 8h v1.25.4
node4 Ready <none> 8h v1.25.4
Далее настраиваем удаленное управление согласно задания на локальной машине и проверяем:
dgolodnikov@pve-vm1:~$ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
dgolodnikov@pve-vm1:~$ chmod +x ./kubectl
dgolodnikov@pve-vm1:~$ sudo mv ./kubectl /usr/local/bin/kubectl
dgolodnikov@pve-vm1:~$ kubectl version -o yaml
clientVersion:
buildDate: "2022-11-09T13:36:36Z"
compiler: gc
gitCommit: 872a965c6c6526caa949f0c6ac028ef7aff3fb78
gitTreeState: clean
gitVersion: v1.25.4
goVersion: go1.19.3
major: "1"
minor: "25"
platform: linux/amd64
kustomizeVersion: v4.5.7
serverVersion:
buildDate: "2022-11-09T13:29:58Z"
compiler: gc
gitCommit: 872a965c6c6526caa949f0c6ac028ef7aff3fb78
gitTreeState: clean
gitVersion: v1.25.4
goVersion: go1.19.3
major: "1"
minor: "25"
platform: linux/amd64
Конфигурацию для kubectl делаем по аналогии, как мы делали это выше. Меняем только адрес кластера с 127.0.0.1 на соответсвенно внешний адрес. В нашем случае это https://158.160.43.129:6443. Проверяем:
dgolodnikov@pve-vm1:~$ kubectl cluster-info
Kubernetes control plane is running at https://158.160.43.129:6443
dgolodnikov@pve-vm1:~/.kube$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
cp1 Ready control-plane 8h v1.25.4
node1 Ready <none> 8h v1.25.4
node2 Ready <none> 8h v1.25.4
node3 Ready <none> 8h v1.25.4
node4 Ready <none> 8h v1.25.4
Выполним тестовое развертывание:
dgolodnikov@pve-vm1:~/.kube$ kubectl create deploy nginx --image=nginx:latest --replicas=3
deployment.apps/nginx created
dgolodnikov@pve-vm1:~/.kube$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-6d666844f6-67mtt 0/1 ContainerCreating 0 8s
nginx-6d666844f6-7jhhp 0/1 ContainerCreating 0 8s
nginx-6d666844f6-klrc2 0/1 ContainerCreating 0 8s
dgolodnikov@pve-vm1:~/.kube$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-6d666844f6-67mtt 1/1 Running 0 48s
nginx-6d666844f6-7jhhp 1/1 Running 0 48s
nginx-6d666844f6-klrc2 1/1 Running 0 48s
dgolodnikov@pve-vm1:~/.kube$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6d666844f6-67mtt 1/1 Running 0 62s 10.233.71.2 node3 <none> <none>
nginx-6d666844f6-7jhhp 1/1 Running 0 62s 10.233.74.66 node4 <none> <none>
nginx-6d666844f6-klrc2 1/1 Running 0 62s 10.233.102.130 node1 <none> <none>
Список подов в namespace=kube-system:
dgolodnikov@pve-vm1:~$ kubectl get pods --namespace=kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
calico-kube-controllers-d6484b75c-m7b6g 1/1 Running 0 9h 10.233.71.1 node3 <none> <none>
calico-node-gqccv 1/1 Running 0 9h 192.168.250.37 node3 <none> <none>
calico-node-k7fzj 1/1 Running 0 9h 192.168.250.9 node4 <none> <none>
calico-node-mw75g 1/1 Running 0 9h 192.168.250.25 node1 <none> <none>
calico-node-xpgn7 1/1 Running 0 9h 192.168.250.10 cp1 <none> <none>
calico-node-zvj52 1/1 Running 0 9h 192.168.250.19 node2 <none> <none>
coredns-588bb58b94-7nzg8 1/1 Running 0 9h 10.233.75.1 node2 <none> <none>
coredns-588bb58b94-86l5v 1/1 Running 0 9h 10.233.102.129 node1 <none> <none>
dns-autoscaler-5b9959d7fc-nvdwg 1/1 Running 0 9h 10.233.74.65 node4 <none> <none>
kube-apiserver-cp1 1/1 Running 1 9h 192.168.250.10 cp1 <none> <none>
kube-controller-manager-cp1 1/1 Running 2 (9h ago) 9h 192.168.250.10 cp1 <none> <none>
kube-proxy-9ngmj 1/1 Running 0 8h 192.168.250.9 node4 <none> <none>
kube-proxy-ggbqh 1/1 Running 0 8h 192.168.250.19 node2 <none> <none>
kube-proxy-kngkd 1/1 Running 0 8h 192.168.250.10 cp1 <none> <none>
kube-proxy-ph72w 1/1 Running 0 8h 192.168.250.25 node1 <none> <none>
kube-proxy-zmm62 1/1 Running 0 8h 192.168.250.37 node3 <none> <none>
kube-scheduler-cp1 1/1 Running 2 (9h ago) 9h 192.168.250.10 cp1 <none> <none>
nginx-proxy-node1 1/1 Running 0 9h 192.168.250.25 node1 <none> <none>
nginx-proxy-node2 1/1 Running 0 9h 192.168.250.19 node2 <none> <none>
nginx-proxy-node3 1/1 Running 0 9h 192.168.250.37 node3 <none> <none>
nginx-proxy-node4 1/1 Running 0 9h 192.168.250.9 node4 <none> <none>
nodelocaldns-96sjv 1/1 Running 0 9h 192.168.250.37 node3 <none> <none>
nodelocaldns-9pthr 1/1 Running 0 9h 192.168.250.10 cp1 <none> <none>
nodelocaldns-btq6j 1/1 Running 0 9h 192.168.250.19 node2 <none> <none>
nodelocaldns-rkbdd 1/1 Running 0 9h 192.168.250.25 node1 <none> <none>
nodelocaldns-s5ph7 1/1 Running 0 9h 192.168.250.9 node4 <none> <none>
Часть новых проектов хотят запускать на мощностях AWS. Требования похожи:
- разворачивать 5 нод: 1 мастер и 4 рабочие ноды;
- работать должны на минимально допустимых EC2 — t3.small.
Для данного задания было решено выполнить развертывание в домашней виртуальной среде ProxmoxVE с помощью kubeadm c CNI flannel.
Адресация виртуальных машин: 192.168.8.30, 192.168.8.31, 192.168.8.32.
Подготовка всех виртульных машин выполнялась по одной схеме, а именно:
Добавление модулей и опций ядра:
dgolodnikov@pve-kube-cp1:~$ echo "br_netfilter" | sudo tee -a /etc/modules
dgolodnikov@pve-kube-cp1:~$ echo "overlay" | sudo tee -a /etc/modules
dgolodnikov@pve-kube-cp1:~$ echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
dgolodnikov@pve-kube-cp1:~$ echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf
dgolodnikov@pve-kube-cp1:~$ echo "net.bridge.bridge-nf-call-arptables=1" | sudo tee -a /etc/sysctl.conf
dgolodnikov@pve-kube-cp1:~$ echo "net.bridge.bridge-nf-call-ip6tables=1" | sudo tee -a /etc/sysctl.conf
dgolodnikov@pve-kube-cp1:~$ cat /etc/modules
# a bit deleted
br_netfilter
overlay
dgolodnikov@pve-kube-cp1:~$ cat /etc/sysctl.conf
# a bit deleted
net.ipv4.ip_forward=1
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-ip6tables=1
Отключение swap (без отключения не стартует kubelet) через комментирование строки в /etc/fstab:
dgolodnikov@pve-kube-cp1:~$ cat /etc/fstab
# a bit deleted
#/swap.img none swap sw 0 0
Корректировка файла /etc/hosts:
dgolodnikov@pve-kube-cp1:~$ cat /etc/hosts
# a bit deleted
127.0.1.1 pve-kube-cp1
192.168.8.30 pve-kube-cp1
Корректировка файла /etc/hostname:
dgolodnikov@pve-kube-cp1:~$ cat /etc/hostname
pve-kube-cp1
Выполняем ребут ВМ (можно обойтись без reboot выполвнив команды sudo sysctl -p /etc/sysctl.conf && sudo swapoff -a):
sudo reboot
Подготовка к установке кластера:
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt install -y kubelet kubeadm kubectl containerd
sudo apt-mark hold kubelet kubeadm kubectl
Создаем дефолт конфигурации containerd и перезапускаем сервис:
sudo -- sh -c "containerd config default | tee /etc/containerd/config.toml"
sudo systemctl restart containerd.service
Запускаем установку для мастер ноды (только для controlplane node):
dgolodnikov@pve-kube-cp1:~$ sudo kubeadm init --apiserver-advertise-address=192.168.8.30 --pod-network-cidr 10.244.0.0/16 --apiserver-cert-extra-sans=MY_PUBLIC_IP
Настраиваем kubectl (только для controlplane node):
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Проверяем:
dgolodnikov@pve-kube-cp1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
pve-kube-cp1 NotReady control-plane 74s v1.25.4
Устанавливаем сетевой плагин (только для controlplane node)
dgolodnikov@pve-kube-cp1:~$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
namespace/kube-flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
Проверяем, мастер нода Ready:
dgolodnikov@pve-kube-cp1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
pve-kube-cp1 Ready control-plane 3m44s v1.25.4
Для настройки рабочих нод необходимо создать токен на мастере:
kubeadm token create --print-join-command
kubeadm join 192.168.8.30:6443 --token j1fpu8.n9wna1cgpd98dxio \
--discovery-token-ca-cert-hash sha256:251edbd9fa258679ec91a38f35cf6482a48437041e225519084f19d5f4d11eda
На рабочих нодах запускаем (только для workernode):
sudo kubeadm join 192.168.8.30:6443 --token n3knr7.pixtgfppw1mr1p7e --discovery-token-ca-cert-hash sha256:e01d39cc353485c09e4476232e4cfce582fe4c0f9fef78c82c1eff8f69b22e25
Проверяем на control node:
dgolodnikov@pve-kube-cp1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
pve-kube-cp1 Ready control-plane 22h v1.25.4
pve-kube-node1 Ready <none> 65s v1.25.4
pve-kube-node2 Ready <none> 38s v1.25.4
dgolodnikov@pve-kube-cp1:~$ kubectl get pods --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-flannel kube-flannel-ds-fdxhb 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
kube-flannel kube-flannel-ds-zcrdr 1/1 Running 0 110s 192.168.8.31 pve-kube-node1 <none> <none>
kube-flannel kube-flannel-ds-zpwgs 1/1 Running 0 83s 192.168.8.32 pve-kube-node2 <none> <none>
kube-system coredns-565d847f94-82tww 1/1 Running 1 (21h ago) 22h 10.244.0.4 pve-kube-cp1 <none> <none>
kube-system coredns-565d847f94-ch7rb 1/1 Running 1 (21h ago) 22h 10.244.0.5 pve-kube-cp1 <none> <none>
kube-system etcd-pve-kube-cp1 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
kube-system kube-apiserver-pve-kube-cp1 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
kube-system kube-controller-manager-pve-kube-cp1 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
kube-system kube-proxy-67h2s 1/1 Running 0 83s 192.168.8.32 pve-kube-node2 <none> <none>
kube-system kube-proxy-bk27z 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
kube-system kube-proxy-sd2lj 1/1 Running 0 110s 192.168.8.31 pve-kube-node1 <none> <none>
kube-system kube-scheduler-pve-kube-cp1 1/1 Running 1 (21h ago) 22h 192.168.8.30 pve-kube-cp1 <none> <none>
PS: Пока нет понимания как делать кластер из несколько ControlPlane node. Предполагаю решение крайне сложно в траблшутинге в случае проблем, в том числе сетевых. Пока сложно представляется этот процесс, кроме как посмотреть логи и пойти в Гугл.