Bare-metal klaster z Kubernetes w 10 minut…?
Pozazdrościłem koledze Sławkowi Wolakowi technicznego artykułu dot. kontenerów i RexRay i pomyślałem, że nie będę gorszy i również napiszę o czymś ze stajni Dockera. Dodatkowym motorem był mój wykład na poznańskim spotkaniu grupy Amazon Web Services (tutaj relacja), gdzie wykorzystałem Ansible do utworzenia środowiska Continuous Integration i Continuous Deployment na bazie klastra z Docker Swarm i Gitlab jako narzędzia do budowania, testowania i wrzucania obrazów do rejestru.
Po zebraniu tych inspiracji w jeden nurt podjąłem decyzję, że temat na dzisiaj to… bare-metal deployment klastra z Kubernetes na CentOS przy pomocy Ansible . Poza samą brudną robotą postaram się też wykazać również czas postawienia takiego środowiska. Zapraszam.
Jeszcze tylko kilka słów o samym Kubernetes – to otwarta platformą służącą do zarządzania klastrem maszyn z zainstalowanym silnikiem Dockera. Zezwala na automatyczne skalowanie horyzontalne, tworzenie loadbalancerów i usług, łatwy upgrade i rollback wersji aplikacji oraz wiele innych ciekawych smaczków. Zdecydowanie można go postrzegać jako alternatywa dla Docker Swarm i produkt Docker Datacenter. Dobra, jedziemy!
Środowisko laboratoryjne
Środowisko laboratoryjne to mój workstation:
- MacBook Pro (Retina, 13-inch, Early 2015)
- Procesor 2,7 GHz Intel Core i5
- 8 GB 1867 MHz DDR3
Maszyny zostaną stworzone z wykorzystaniem narzędzia na bazie Vagrant (korzystając z providera – VirtualBox), połączone siecią i konfigurowane przy pomocy Ansible.
kubernetes-01 | Kontroler |
kubernetes-02 | Minion |
kubernetes-03 | Minion |
Nadajemy im następującą adresację:
kubernetes-01 | 192.168.50.101 |
kubernetes-02 | 192.168.50.102 |
kubernetes-03 | 192.168.50.103 |
Powołanie środowiska
Prace zaczniemy od stworzenia czystych maszyn wirtualnych w środowisku. Maszyny powołujemy przy pomocy narzędzia Vagrant. W tym celu tworzymy plik Vagrantfile.
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "centos/7" config.vm.box_check_update = true config.vm.synced_folder ".", "/vagrant", disabled: true config.ssh.keys_only = true config.ssh.insert_key = false config.ssh.paranoid = false (1..3).each do |i| config.vm.define "kubernetes-0#{i}" do |j| j.vm.hostname = "kubernetes-0#{i}" j.vm.network "private_network", ip: "192.168.50.10#{i}" j.vm.provision :hosts do |p| p.autoconfigure = true p.sync_hosts = true p.add_localhost_hostnames = false end j.vm.provider "virtualbox" do |v| v.memory = 512 v.cpus = 2 v.gui = false v.name = "kubernetes-0#{i}" v.customize ["modifyvm", :id, "--cpuexecutioncap", "50"] end end end end
Następnie uruchamiamy zdefniowane maszyny wirtualne; wchodzimy do katalogu z plikiem konfiguracyjnym Vagrantfile i uruchamiamy następującą komendę.
$ time (vagrant up) Bringing machine 'kubernetes-01' up with 'virtualbox' provider... Bringing machine 'kubernetes-02' up with 'virtualbox' provider... Bringing machine 'kubernetes-03' up with 'virtualbox' provider... ==> kubernetes-01: Importing base box 'centos/7'... ==> kubernetes-01: Matching MAC address for NAT networking... ==> kubernetes-01: Checking if box 'centos/7' is up to date... ==> kubernetes-01: Setting the name of the VM: kubernetes-01 [...] ==> kubernetes-01: Setting hostname... ==> kubernetes-01: Configuring and enabling network interfaces... ==> kubernetes-01: Running provisioner: hosts... ==> kubernetes-02: Importing base box 'centos/7'... ==> kubernetes-02: Matching MAC address for NAT networking... ==> kubernetes-02: Checking if box 'centos/7' is up to date... ==> kubernetes-02: Setting the name of the VM: kubernetes-02 [...] ==> kubernetes-02: Machine booted and ready! [...] ==> kubernetes-02: Setting hostname... ==> kubernetes-02: Configuring and enabling network interfaces... ==> kubernetes-02: Running provisioner: hosts... ==> kubernetes-01: Updating hosts on: kubernetes-01 ==> kubernetes-03: Importing base box 'centos/7'... ==> kubernetes-03: Matching MAC address for NAT networking... ==> kubernetes-03: Checking if box 'centos/7' is up to date... ==> kubernetes-03: Setting the name of the VM: kubernetes-03 [..] ==> kubernetes-03: Machine booted and ready! [...] ==> kubernetes-03: Setting hostname... ==> kubernetes-03: Configuring and enabling network interfaces... ==> kubernetes-03: Running provisioner: hosts... ==> kubernetes-01: Updating hosts on: kubernetes-01 ==> kubernetes-02: Updating hosts on: kubernetes-02 ( vagrant up; ) 36.69s user 28.12s system 33% cpu 3:11.33 total $
Pro-forma. time nie jest wymagane. Posłuży tylko za dowód czasu stworzenia środowiska.
Wchodząc teraz do konsoli VirtualBox możemy zauważyć maszyny.
Ansible
Zgodnie z tym, co zostało opisane we wstępnie, wykonam pełen provisioning powołanych maszyn wirtualnych za pomocą Ansible. W tym celu utworzyłem plik inwentarza1 o następującej treści:
[kubernetes-master] kubernetes-01 ansible_host=192.168.50.101 [kubernetes-minions] kubernetes-02 ansible_host=192.168.50.102 kubernetes-03 ansible_host=192.168.50.103
Weryfikuję dostępność maszyn z mojej lokalnej maszyny przy pomocy modułu ping.
$ ansible -i inventory.ini -m ping -o all kubernetes-01 | SUCCESS => {"changed": false, "ping": "pong"} kubernetes-03 | SUCCESS => {"changed": false, "ping": "pong"} kubernetes-02 | SUCCESS => {"changed": false, "ping": "pong"} $
Teraz, celem poprawnej konfiguracji maszyn, muszę dostosować zmienne środowiska do swoich ról. W tym celu, wpisuję następujące zmienne środowiskowe w pliku group_vars/all.yml:
--- centsible_etcd_cluster_hosts: 'kubernetes-master' centsible_flanneld_etcd_cluster_hosts: '{{ centsible_etcd_cluster_hosts }}' centsible_kubernetes_master_address: 'http://kubernetes-01:8080' centsible_kubernetes_minion_kubelet_apiserver: '{{ centsible_kubernetes_master_address }}'
Role są zdefiniowane w taki sposób, aby automatycznie na bazie inwentarza oraz przynależności poszczególnych hostów do grup, tworzyły pliki konfiguracyjne klastra. Dlatego w pliku zostały zadeklarowane takie zmienne jak:
centsible_etcd_cluster_hosts
centsible_flanneld_etcd_cluster_hosts
Teraz pozostało stworzyć playbook, który posłuży nam za logiczny opis konfiguracji. Tworzymy, zatem, plik site.yml o następującej treści:
--- - hosts: 'all' become: 'true' gather_facts: 'true' any_errors_fatal: 'true' roles: - 'centsible.repository-kubernetes' - hosts: 'kubernetes-master' become: 'true' gather_facts: 'true' any_errors_fatal: 'true' roles: - 'centsible.etcd' - 'centsible.kubernetes-flanneld' - 'centsible.flanneld' - 'centsible.kubernetes-master' - hosts: 'kubernetes-minions' become: 'true' gather_facts: 'true' any_errors_fatal: 'true' roles: - 'centsible.flanneld' - 'centsible.kubernetes-minion'
Konfiguracja maszyn
Uruchamiamy playbook site.yml. Pełny log dostępny w załączniku.
$ time (ansible-playbook -i inventory.ini site.yml) [...] PLAY RECAP ********************************************************************* kubernetes-01 : ok=19 changed=15 unreachable=0 failed=0 kubernetes-02 : ok=11 changed=9 unreachable=0 failed=0 kubernetes-03 : ok=11 changed=9 unreachable=0 failed=0 ( ansible-playbook -i inventory.ini site.yml; ) 25.97s user 6.44s system 7% cpu 6:48.54 total $
Weryfikacja klastra
Logując się do maszyny kubernetes-01, możemy zweryfikować:
- poprawność działania klastra etcd,
- widoczność minions i ich dostępność do pracy.
[root@kubernetes-01 ~]# etcdctl cluster-health member aa9d9612ca8e7522 is healthy: got healthy result from http://kubernetes-01:2379 cluster is healthy [root@kubernetes-01 ~]#[root@kubernetes-01 ~]# kubectl get nodes NAME STATUS AGE kubernetes-02 Ready 52m kubernetes-03 Ready 52m [root@kubernetes-01 ~]#
Dla ostatecznej weryfikacji klastra wykonajmy testowy deployment. Plik deploymentu wygląda następująco.
--- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
Inicjujemy deployment.
[root@kubernetes-01 ~]# kubectl create -f nginx-deployment.yaml --record deployment "nginx-deployment" created [root@kubernetes-01 ~]# kubectl get deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment 3 3 3 0 10s [root@kubernetes-01 ~]#
Logując się teraz do maszyn pracujących w trybie minion, widzimy dostępność kontenerów i podów.
[root@kubernetes-02 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3732a096c8e8 nginx:1.7.9 "nginx -g 'daemon off" About a minute ago Up About a minute k8s_nginx.4580025_nginx-deployment-2035384211-40xo6_default_af13202f-bf86-11e6-b806-525400d87180_2193112c 64d87909d12d registry.access.redhat.com/rhel7/pod-infrastructure:latest "/pod" About a minute ago Up About a minute k8s_POD.c36b0a77_nginx-deployment-2035384211-40xo6_default_af13202f-bf86-11e6-b806-525400d87180_2d81b186 [root@kubernetes-02 ~]#[vagrant@kubernetes-03 ~]$ sudo -i [root@kubernetes-03 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ac69709d91a8 nginx:1.7.9 "nginx -g 'daemon off" About a minute ago Up About a minute k8s_nginx.4580025_nginx-deployment-2035384211-4hshi_default_af135165-bf86-11e6-b806-525400d87180_d50337b2 17e0e5f3c79f nginx:1.7.9 "nginx -g 'daemon off" About a minute ago Up About a minute k8s_nginx.4580025_nginx-deployment-2035384211-itn8l_default_af130916-bf86-11e6-b806-525400d87180_127d9453 5137b148f407 registry.access.redhat.com/rhel7/pod-infrastructure:latest "/pod" 2 minutes ago Up 2 minutes k8s_POD.c36b0a77_nginx-deployment-2035384211-4hshi_default_af135165-bf86-11e6-b806-525400d87180_bd0b20d4 16af044f1288 registry.access.redhat.com/rhel7/pod-infrastructure:latest "/pod" 2 minutes ago Up 2 minutes k8s_POD.c36b0a77_nginx-deployment-2035384211-itn8l_default_af130916-bf86-11e6-b806-525400d87180_34ffb31e [root@kubernetes-03 ~]#
Absolutnie nic nie stoi na przeszkodzie, aby do ról dodać smoke tests, które będą wykonywały powyższe czynności.
Plik site.yml
Plik site.yml jest playbookiem Ansible i przypisuje maszynom odpowiednie role. Rolą w Ansible nazywamy zestaw zadań, triggerów, zmiennych, akcji, które zostaną wykonane na maszynie. Paramtery takie jak gather_facts czy become nie są istotne z punktu widzenia tematu artykułu.
Intuicyjnie widać, której maszynie jaką rolę przypisałem. Playbook utworzył klaster etcd z jednej maszyny. Zainstalował potrzebne pakiety, wykonał deployment pliku konfiguracyjnego, uruchomił usługę i upewnił się o jej autostarcie.
Podobnie skonfigurował flanneld. Upewnił się o tym, że potrzebne paczki są zainstalowane, nadpisał plik konfiguracyjny, skorelował go z plikiem konfiguracyjnym etcd, wypełnił poprawnymi wartościami i uruchomił usługę.
Tak samo z Kubernetes. Role: centsible.kuberenetes-minion oraz centsible.kubernetes-master odpowiednio skonfigurowały usługi, a na końcu przypisały node do mastra.
Idempotentność
Warto również wykazać idempotentność ról. Idempotentność w kontekście algebry oznacza nie więcej jak niezmienniczość ze względu na wynik przy dwukrotnym zastosowaniu tej samej funkcji. Tutaj jest nie inaczej. Jeśli dwukrotnie zastosujemy rolę, oczekujemy, że nie będzie zmian w środowisku, chyba, że stan środowiska się zmienił.
Ponownie uruchomienie tego samego playbooka skutkuje stanem OK na każdym zadaniu. Oznacza to, że cała konfiguracja klastra jest odwzorowana kodem. Idempotentność została wykazana w załączniku.
Czas budowy klastra
Sumując dwie metryki mamy: 6:48.54 +
3:11.33 ~ 10 min.
Pewnie gdybym usunął to ograniczenie na procesor i przydzielił trochę więcej RAMu, zszedłbym poniżej tej wartości. 😉
Słowem końcowym
Oczywiście, artykuł można bardzo szeroko rozszerzyć. Mógłbym wykazać jak tymi samymi rolami rozszerzać klaster horyzontalnie, zarówno w roli master jak i node. Rozszerzyć klaster do pełnego high-availability i wykazać redundancję. Dołożyć rolę od kluczy i wymagać autoryzacji certyfikatem klienckim. Dodać monitoring. Pokazać Kubernetes dashboard.
Jeśli tylko będzie zapotrzebowanie; nie omieszkam rozszerzyć tego artykułu.
Referencje
W artykule wykorzystałem następujące technologie:
Załączniki
Uruchomienie site.yml
$ time (ansible-playbook -i inventory.ini site.yml) PLAY [all] ********************************************************************* TASK [setup] ******************************************************************* ok: [kubernetes-01] ok: [kubernetes-02] ok: [kubernetes-03] TASK [centsible.repository-kubernetes : ensure public keys are present] ******** changed: [kubernetes-02] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) changed: [kubernetes-03] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) changed: [kubernetes-01] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) changed: [kubernetes-01] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) changed: [kubernetes-02] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) changed: [kubernetes-03] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) TASK [centsible.repository-kubernetes : ensure repository is present] ********** changed: [kubernetes-03] changed: [kubernetes-02] changed: [kubernetes-01] PLAY [kubernetes-master] ******************************************************* TASK [setup] ******************************************************************* ok: [kubernetes-01] TASK [centsible.etcd : ensure packages are installed] ************************** changed: [kubernetes-01] => (item=[u'etcd']) TASK [centsible.etcd : ensure data directories are present] ******************** changed: [kubernetes-01] => (item=/var/lib/etcd/data) changed: [kubernetes-01] => (item=/var/lib/etcd/wal) TASK [centsible.etcd : deploy configuration file] ****************************** changed: [kubernetes-01] TASK [centsible.etcd : ensure services are running] **************************** changed: [kubernetes-01] => (item=etcd) TASK [centsible.kubernetes-flanneld : ensure flanneld config dir is present in etcd] *** changed: [kubernetes-01] TASK [centsible.kubernetes-flanneld : ensure flanneld config key is present in etcd] *** ok: [kubernetes-01] TASK [centsible.flanneld : ensure packages are installed] ********************** changed: [kubernetes-01] => (item=[u'flannel']) TASK [centsible.flanneld : deploy configuration file] ************************** changed: [kubernetes-01] TASK [centsible.flanneld : ensure service is started] ************************** changed: [kubernetes-01] => (item=flanneld) TASK [centsible.kubernetes-master : ensure packages are installed] ************* changed: [kubernetes-01] => (item=[u'kubernetes-master']) TASK [centsible.kubernetes-master : deploy /etc/kubernetes/config] ************* changed: [kubernetes-01] TASK [centsible.kubernetes-master : deploy /etc/kubernetes/apiserver] ********** changed: [kubernetes-01] TASK [centsible.kubernetes-master : ensure services are up and running] ******** changed: [kubernetes-01] => (item=kube-apiserver) changed: [kubernetes-01] => (item=kube-controller-manager) changed: [kubernetes-01] => (item=kube-scheduler) RUNNING HANDLER [centsible.etcd : restart etcd] ******************************** changed: [kubernetes-01] => (item=etcd) RUNNING HANDLER [centsible.kubernetes-master : restart kubernetes-master] ****** ok: [kubernetes-01] => (item=kube-apiserver) ok: [kubernetes-01] => (item=kube-controller-manager) ok: [kubernetes-01] => (item=kube-scheduler) PLAY [kubernetes-minions] ****************************************************** TASK [setup] ******************************************************************* ok: [kubernetes-02] ok: [kubernetes-03] TASK [centsible.flanneld : ensure packages are installed] ********************** changed: [kubernetes-03] => (item=[u'flannel']) changed: [kubernetes-02] => (item=[u'flannel']) TASK [centsible.flanneld : deploy configuration file] ************************** changed: [kubernetes-02] changed: [kubernetes-03] TASK [centsible.flanneld : ensure service is started] ************************** changed: [kubernetes-02] => (item=flanneld) changed: [kubernetes-03] => (item=flanneld) TASK [centsible.kubernetes-minion : ensure packages are installed] ************* changed: [kubernetes-02] => (item=[u'kubernetes-node']) changed: [kubernetes-03] => (item=[u'kubernetes-node']) TASK [centsible.kubernetes-minion : deploy /etc/kubernetes/kubelet] ************ changed: [kubernetes-02] changed: [kubernetes-03] TASK [centsible.kubernetes-minion : ensure services are up and running] ******** changed: [kubernetes-03] => (item=kube-proxy) changed: [kubernetes-02] => (item=kube-proxy) changed: [kubernetes-03] => (item=kubelet) changed: [kubernetes-02] => (item=kubelet) RUNNING HANDLER [centsible.kubernetes-minion : restart kubernetes-minion] ****** changed: [kubernetes-03] => (item=kube-proxy) changed: [kubernetes-02] => (item=kube-proxy) changed: [kubernetes-03] => (item=kubelet) changed: [kubernetes-02] => (item=kubelet) PLAY RECAP ********************************************************************* kubernetes-01 : ok=19 changed=15 unreachable=0 failed=0 kubernetes-02 : ok=11 changed=9 unreachable=0 failed=0 kubernetes-03 : ok=11 changed=9 unreachable=0 failed=0 ( ansible-playbook -i inventory.ini site.yml; ) 25.97s user 6.44s system 7% cpu 6:48.54 total $
Idempotentność
$ time (ansible-playbook -i inventory.ini site.yml) PLAY [all] ********************************************************************* TASK [setup] ******************************************************************* ok: [kubernetes-01] ok: [kubernetes-03] ok: [kubernetes-02] TASK [centsible.repository-kubernetes : ensure public keys are present] ******** ok: [kubernetes-01] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) ok: [kubernetes-03] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) ok: [kubernetes-02] => (item=https://packages.cloud.google.com/yum/doc/yum-key.gpg) ok: [kubernetes-01] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) ok: [kubernetes-03] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) ok: [kubernetes-02] => (item=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg) TASK [centsible.repository-kubernetes : ensure repository is present] ********** ok: [kubernetes-01] ok: [kubernetes-02] ok: [kubernetes-03] PLAY [kubernetes-master] ******************************************************* TASK [setup] ******************************************************************* ok: [kubernetes-01] TASK [centsible.etcd : ensure packages are installed] ************************** ok: [kubernetes-01] => (item=[u'etcd']) TASK [centsible.etcd : ensure data directories are present] ******************** ok: [kubernetes-01] => (item=/var/lib/etcd/data) ok: [kubernetes-01] => (item=/var/lib/etcd/wal) TASK [centsible.etcd : deploy configuration file] ****************************** ok: [kubernetes-01] TASK [centsible.etcd : ensure services are running] **************************** ok: [kubernetes-01] => (item=etcd) TASK [centsible.kubernetes-flanneld : ensure flanneld config dir is present in etcd] *** ok: [kubernetes-01] TASK [centsible.kubernetes-flanneld : ensure flanneld config key is present in etcd] *** ok: [kubernetes-01] TASK [centsible.flanneld : ensure packages are installed] ********************** ok: [kubernetes-01] => (item=[u'flannel']) TASK [centsible.flanneld : deploy configuration file] ************************** ok: [kubernetes-01] TASK [centsible.flanneld : ensure service is started] ************************** ok: [kubernetes-01] => (item=flanneld) TASK [centsible.kubernetes-master : ensure packages are installed] ************* ok: [kubernetes-01] => (item=[u'kubernetes-master']) TASK [centsible.kubernetes-master : deploy /etc/kubernetes/config] ************* ok: [kubernetes-01] TASK [centsible.kubernetes-master : deploy /etc/kubernetes/apiserver] ********** ok: [kubernetes-01] TASK [centsible.kubernetes-master : ensure services are up and running] ******** ok: [kubernetes-01] => (item=kube-apiserver) ok: [kubernetes-01] => (item=kube-controller-manager) ok: [kubernetes-01] => (item=kube-scheduler) PLAY [kubernetes-minions] ****************************************************** TASK [setup] ******************************************************************* ok: [kubernetes-02] ok: [kubernetes-03] TASK [centsible.flanneld : ensure packages are installed] ********************** ok: [kubernetes-02] => (item=[u'flannel']) ok: [kubernetes-03] => (item=[u'flannel']) TASK [centsible.flanneld : deploy configuration file] ************************** ok: [kubernetes-02] ok: [kubernetes-03] TASK [centsible.flanneld : ensure service is started] ************************** ok: [kubernetes-03] => (item=flanneld) ok: [kubernetes-02] => (item=flanneld) TASK [centsible.kubernetes-minion : ensure packages are installed] ************* ok: [kubernetes-03] => (item=[u'kubernetes-node']) ok: [kubernetes-02] => (item=[u'kubernetes-node']) TASK [centsible.kubernetes-minion : deploy /etc/kubernetes/kubelet] ************ ok: [kubernetes-03] ok: [kubernetes-02] TASK [centsible.kubernetes-minion : ensure services are up and running] ******** ok: [kubernetes-03] => (item=kube-proxy) ok: [kubernetes-02] => (item=kube-proxy) ok: [kubernetes-03] => (item=kubelet) ok: [kubernetes-02] => (item=kubelet) PLAY RECAP ********************************************************************* kubernetes-01 : ok=19 changed=0 unreachable=0 failed=0 kubernetes-02 : ok=10 changed=0 unreachable=0 failed=0 kubernetes-03 : ok=10 changed=0 unreachable=0 failed=0 ( ansible-playbook -i inventory.ini site.yml; ) 6.28s user 2.05s system 22% cpu 37.068 total $
- Dla osób zaznajomionych z Ansible. Jest możliwość wykorzystywania dynamicznego inwentarza dla Vagrant. Takie skrypty są dostępne publicznie.↩
artykuł rewelacja
Pingback: Sterowanie pogodą, czyli jak zarządzać chmurami? Odcinek 2. » Inleo - Infrastruktura i architektura IT, Konteneryzacja, Datacenter, Private Cloud, PM