We wstępnie zapoznaliśmy się z firmą Hetzner oraz z plusami usług tej firmy. Teraz czas, aby przejść do sedna, czyli jak postawić klaster k8s na tej platformie. Zaczynamy… Wchodzimy na stronę https://hetzner.cloud/?ref=CM3Ec8fo6mU6 (jest to link polecający, który da Ci na start 20 €), następnie Login -> Cloud -> Register now.
Po utworzeniu konta, logujemy się i tworzymy nowy projekt. Ja swój nazwałem k8s. Zamykanie infrastruktury w projekty znacznie ułatwi kontrolę kosztów.
Następnie w nowo utworzonym projekcie przechodzimy do Security -> Api tokens -> Generate API token -> nadajemy mu jakąś nazwę i wybieramy opcję „Read & Write”. Wygenerowany klucz dodajemy jako zmienną.
HCLOUD_TOKEN="<YOUR_TOKEN>"
Instalujemy znakomite narzędzie do obsługi Hetzner Cloud. Dzięki niemu nie będziemy musieli już zaglądać na ich stronę. W zależności od Twojego systemu instalacja będzie wyglądać nieco inaczej. Wszystkie potrzebne informacje są na oficjalnej stronie: https://github.com/hetznercloud/cli#installation
Logujemy się do Hetzner Cloud przy pomocy nowego tokenu.
hcloud context create <PROJECT_NAME>
Dodajmy nasz klucz publiczny. Jest to ważne (i zwiększa bezpieczeństwo), ponieważ ten klucz zostanie automatycznie dodany na każdy nowo utworzony serwer. Dzięki temu firma Hetzner nie wyśle nam maila z hasłem roota, a zalogujemy się na serwer swoim kluczem. Jeżeli nie wiesz, jak utworzyć swoją parę kluczy, zrób to według na przykład tej instrukcji https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
hcloud ssh-key create --name <YOUR_NAME>@<MACHINE_NAME> --public-key-from-file ~/.ssh/id_rsa.pub
Na początku utworzymy sieć, z której nowe serwery dostaną prywatne adresy IP. Do tej sieci podepniemy nasze serwery, dzięki temu komunikacja pomiędzy nimi odbywać się będzie tylko w obrębie tej prywatnej sieci i nie wyjdzie na zewnątrz.
hcloud network create --name $(hcloud context active) --ip-range 10.98.0.0/16 && \
hcloud network add-subnet $(hcloud context active) --network-zone eu-central --type server --ip-range 10.98.0.0/16
Tworzymy serwery, z których będzie się składał nasz klaster. Jak pisałem we wstępie, mój klaster składa się z jednego node master (CX11 – 1 CPU, 2 GB RAM) oraz trzech worker node (CX21 – 2 CPU, 4 GB RAM). Jako lokalizację wybrałem Niemcy, Falkenstein (fsn1-dc14), jako że jest najbliżej Polski. Polecenia te automatycznie utworzą serwery z systemem Ubuntu 20.04 i dodadzą wcześniej utworzony klucz i sieć.
hcloud server create --type cx11 --datacenter fsn1-dc14 --name master-1 --image ubuntu-20.04 --ssh-key $(hcloud ssh-key list -o noheader -o columns=id) --network $(hcloud network list -o noheader -o columns=id) && \
hcloud server create --type cx21 --datacenter fsn1-dc14 --name worker-1 --image ubuntu-20.04 --ssh-key $(hcloud ssh-key list -o noheader -o columns=id) --network $(hcloud network list -o noheader -o columns=id) && \
hcloud server create --type cx21 --datacenter fsn1-dc14 --name worker-2 --image ubuntu-20.04 --ssh-key $(hcloud ssh-key list -o noheader -o columns=id) --network $(hcloud network list -o noheader -o columns=id) && \
hcloud server create --type cx21 --datacenter fsn1-dc14 --name worker-3 --image ubuntu-20.04 --ssh-key $(hcloud ssh-key list -o noheader -o columns=id) --network $(hcloud network list -o noheader -o columns=id)
Dla bezpieczeństwa najpierw uaktualnimy wszystkie pakiety do najnowszych wersji. Polecenie to loguje się do każdego serwera przez ssh i uaktualnia cały system.
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
ssh -n root@$ip apt-get update; \
ssh -n root@$ip apt-get -y dist-upgrade; \
ssh -n root@$ip reboot; \
done
Musimy się upewnić, że klaster może się komunikować pomiędzy nodami i podami.
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
printf "# Allow IP forwarding for kubernetes\nnet.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1\n" \
| ssh root@$ip -T "cat > /etc/sysctl.conf"; \
ssh -n root@$ip modprobe br_netfilter; \
ssh -n root@$ip sysctl -p; \
done
Przygotowujemy konfigurację kubleta, by w przyszłości używał Cloud Controller Manager, który zintegruje klaster z Hetzner API.
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
ssh -n root@$ip mkdir /etc/systemd/system/kubelet.service.d; \
printf "[Service]\nEnvironment=\"KUBELET_EXTRA_ARGS=--cloud-provider=external\"\n" \
| ssh root@$ip -T "cat > /etc/systemd/system/kubelet.service.d/20-hetzner-cloud.conf"; \
done
Następnie instalujemy Dockera (wersja 19.03.15) i Kubernetes (wersja 1.19.12). Dla pewności, gdyby przyszłe wersje były niekompatybilne z implementacjami CNI lub CSI Hetznera.
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
ssh -n root@$ip mkdir /etc/systemd/system/docker.service.d; \
printf "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd\n" \
| ssh root@$ip -T "cat > /etc/systemd/system/docker.service.d/00-cgroup-systemd.conf"; \
ssh -n root@$ip systemctl daemon-reload; \
done
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
hcloud server list -o columns=ipv4 -o noheader \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
ssh -n root@$ip "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -"; \
ssh -n root@$ip "curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -"; \
printf "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable\ndeb http://packages.cloud.google.com/apt/ kubernetes-xenial main\n" \
| ssh root@$ip -T "cat > /etc/apt/sources.list.d/docker-and-kubernetes.list"; \
ssh -n root@$ip apt-get update; \
ssh -n root@$ip apt-get install -y docker-ce=5:19.03.15~3-0~ubuntu-focal kubeadm=1.19.12-00 kubectl=1.19.12-00 kubelet=1.19.12-00; \
done
Po wszystkim musimy się zalogować na serwer master i zainicjować klaster.
ssh -n root@$(hcloud server list | grep master | awk '{print $4}') kubeadm config images pull; \
ssh -n root@$(hcloud server list | grep master | awk '{print $4}') kubeadm init \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=10.244.0.0/16 \
--kubernetes-version=v1.19.12 \
--ignore-preflight-errors=NumCPU \
--apiserver-cert-extra-sans=$(hcloud server describe -o json master-1 | jq -r .private_net[0].ip)
Pod koniec polecenia kubeadm join na masterze została wyświetlona komenda w formacie podanym poniżej. Należy ją skopiować, przyda nam się później do podłączenia worker nodów.
kubeadm join <MASTER_NODE_IP>:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<CA-CERT-HASH>
Następnie kopiujemy na lokalną maszynę konfigurację potrzebną do umożliwienia komunikacji z klastrem. Dzięki temu kolejne polecenia kubectl można już przeprowadzać z poziomu lokalnej maszyny.
scp root@$(hcloud server list | grep master | awk '{print $4}'):/etc/kubernetes/admin.conf ${HOME}/.kube/config
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 NotReady master 5m28s v1.19.12
Master jest na miejscu, ale ma status NotReady – dlaczego? Ponieważ nie skonfigurowaliśmy sieci potrzebnych by nody mogły się komunikować. W naszym przypadku CNI (Container Network Interface) będzie implementował flannel. Instalujemy go poleceniem:
kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml
Teraz jesteśmy gotowi, by zalogować się na worker nody i użyć komendy zapisanej po wykonaniu kubectl init. Jeżeli zapomniałeś jej zapisać, można ją odzyskać, wykonując polecenie poniżej na master nodzie.
kubeadm token create --print-join-command
Polecenie będzie mieć postać
hcloud server list | grep worker | awk '{print $4}' \
| while read -r ip; \
do printf "#######\nUpdating $ip\n#######\n"; \
ssh -n root@$ip kubeadm join <MASTER_NODE_IP>:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<CA-CERT-HASH>; \
done
Po wszystkim jeszcze raz sprawdzamy stan naszych nodów, wygląda lepiej, ale po bliższym przyjrzeniu zobaczymy, że klaster nie jest jeszcze w pełni gotowy do działania.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready master 8m38s v1.19.12
worker-1 Ready <none> 6m32s v1.19.12
worker-2 Ready <none> 6m14s v1.19.12
worker-3 Ready <none> 5m42s v1.19.12
$ kubectl describe node worker-1 | grep Taints
Taints: node.cloudprovider.kubernetes.io/uninitialized=true:NoSchedule
Controller-manager dodał flagę „uninitialized”, ponieważ uznał, że nody nie są jeszcze gotowe. Aby ten stan się zmienił sieci oraz DNS muszą działać poprawne, aktualnie coredns czeka na noda, gdzie może zostać zainstalowany. Musimy na nim wymusić, aby zaakceptował niezainicjalizowane nody.
$ kubectl get po -n kube-system | grep coredns
coredns-f9fd979d6-8dk48 0/1 Pending 0 35m
coredns-f9fd979d6-x6c9z 0/1 Pending 0 35m
kubectl -n kube-system patch daemonset kube-flannel-ds --type json -p '[{"op":"add","path":"/spec/template/spec/tolerations/-","value":{"key":"node.cloudprovider.kubernetes.io/uninitialized","value":"true","effect":"NoSchedule"}}]' && \
kubectl -n kube-system patch deployment coredns --type json -p '[{"op":"add","path":"/spec/template/spec/tolerations/-","value":{"key":"node.cloudprovider.kubernetes.io/uninitialized","value":"true","effect":"NoSchedule"}}]'
W kolejnej części pokażę Ci, jak skonfigurować sieć, tak by wystawić nasz klaster na świat zewnętrzny oraz sprawdzimy, czy uda nam się stworzyć automatycznie volumen z danymi na serwerach Hetzner. Jeżeli podobał Ci się ten artykuł, daj znać w komentarzu.
0 komentarzy