CI/CD pipeline z użyciem Kubernetesa, AWS, Azure i .NET Core – stawianie klastra na AWS

Opublikowane przez admin w dniu

Cześć!

Zapraszam na nową serię wpisów dotyczącej stworzenia pipeline CI/CD z użyciem Kubernetesa, chmury AWS, Azure i .NET Core. Pierwszy wpis poruszy temat stawiania klastra w chmurze AWS.

Seria CI/CD pipeline z użyciem Kubernetesa, AWS, Azure i .NET Core:

  1. Stawianie klastra na AWS
  2. Helm 3.0 i ACR
  3. Dodawanie klastra w Octopus Deploy
  4. Deploy na klaster testowy
  5. Stawianie klastra na Azure oraz deploy

Przegląd architektury

Cały pipeline będzie miał następujący przepływ:

  1. Wypchnięcie kodu do repozytorium GitHub.
  2. Zbudowanie aplikacji w Azure Devops.
  3. Wypchnięcie obrazu dockerowego do prywatnego registry.
  4. Deploy paczki Helmowej zawierającej obraz aplikacji na klaster testowy kubernetesa w AWS przy użyciu Octopus Deploy.
  5. Ręczny promote aplikacji na produkcyjny klaster kubernetesa w Azure przy użyciu Octopus Deploy.

Dodatkowo, oba klastry będą zarządzane z poziomu Ranchera. W czerwonej ramce znajduję się zakres tego posta.

Wprowadzenie

Do postawienia klastra użyję narzędzia kops. Istnieją także alternatywy w postaci narzędzi kubeadm czy kubespray, które potrafią także postawić klaster na naszej wewnętrznej infrastrukturze. Oczywiście, każdy z “wielkiej trójki chmurowej” (Azure, AWS, GCP) dostarcza opcje zarządzalnego klastra, który można postawić kilkoma kliknięciami, jednak ja wybrałem opcję, gdzie sami będziemy zarządzać maszynami typu master, ponieważ w zamyśle będzie to klaster dla środowiska testowego. Taki klaster będzie także tańszy. Jeśli chodzi o koszty kubernetesa w chmurze, to polecam szeroki artykuł na ten temat: the-ultimate-kubernetes-cost-guide.

Potrzebne narzędzia

Przed przystąpieniem do stawiania klastra będzie potrzebna instalacja kilku narzędzi. Linki i instrukcje zamieszczam poniżej:

Narzędzie  kops nie wspiera systemu operacyjnego windows, więc jeśli nie pracujesz na linuxie/macu, będziesz zmuszony do postawienia maszyny wirtualnej z Ubuntu. W moim przypadku był to EC2 t2.micro na AWS.

aws-cli 

apt install python3-pip

pip3 install awscli --upgrade --user

aws --version

 

kubectl

curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl

chmod +x ./kubectl

sudo mv ./kubectl /usr/local/bin/kubectl

kubectl version

 

kops

curl -Lo kops https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64

chmod +x ./kops

sudo mv ./kops /usr/local/bin/

 

Konfiguracja wstępna

Całą konfiguracje aws możemy przeprowadzić w konsoli, jednak będziemy to robić tylko raz, więc osobiście wybrałem UI. Logujemy się więc na nasze konto aws (https://console.aws.amazon.com).

IAM User

Aby kops mógł utworzyć klaster maszyn w aws, musimy stworzyć użytkownika z odpowiednimi uprawnieniami. Możemy to zrobić pod tym linkiem: https://console.aws.amazon.com/iam/home#/users$new?step=details lub po prostu znaleźć zasób IAM w awsie. Wybieramy nazwę użytkownika oraz zaznaczamy  ‘Access type’ na ‘Programmatic access’.

 

W następnym kroku musimy nadać uprawnienia. Możemy stworzyć grupę, do której dodamy naszego użytkownika lub dopisać role bezpośrednio. Role, które będą potrzebne to:

AmazonEC2FullAccess
AmazonRoute53FullAccess
AmazonS3FullAccess
IAMFullAccess
AmazonVPCFullAccess

Krok z tagami możemy pominąć. Po dodaniu użytkownika zapisujemy access key ID i secret key. Będą nam wkrótce potrzebne.

Przechodzimy do konsoli, gdzie zainstalowaliśmy aws-cli i wpisujemy następującą komendę:

aws configure

Wpisujemy access key IDoraz secret key użytkownika, którego przed momentem stworzyliśmy. W pole `Default region name` wpisujemy naszą nazwę regionu. W moim przypadku był to `eu-central-1`.

Konfiguracja DNS

Ten krok jest opcjonalny. We wcześniejszych wersjach kops (poniżej 1.6.2) wymagał istnienia realnej domeny. Na jej podstawie tworzy on wpisy DNS, aby API kubernetesa było dostępne publicznie pod domeną łatwą do zapamiętania. Dodatkowo, tworzone są także wpisy użyteczne do komunikacji wewnętrznej pomiędzy węzłami samego kubernetesa, jak i pomiędzy węzłami rozproszonej bazy danych, której kubernetes używa wewnętrznie – etcd. Jeśli nie chcemy/nie mamy takiej domeny, to od wersji wyższej niż 1.6.2 możemy użyć subdomeny `k8s.local`. W tym przypadku kops utworzy loadbalancer w AWS wskazujący na węzły master, dzięki czemu będzie można zarządzać klastrem z zewnątrz. Oczywiście, taki loadbalancer to dodatkowy koszt oraz tworzony jest on pod losową, trudną do zapamiętania domeną. W tym przykładzie posłużę się realną domeną oraz pokażę jak skonfigurować ją w AWS.

Na potrzeby wpisu założyłem darmową domenę k8s.com.pl w serwisie nazwa.pl.

Potrzebne będzie przekierowanie naszej domeny na dnsy chmury AWS. Aby to robić w konsoli wyszukujemy usługę Route 53 (https://console.aws.amazon.com/route53/home). Wybieramy DNS Management a na następnej stronie klikamy Create Hosted Zone.

 

Tworzymy naszą Hosted Zone . Po stworzeniu kopiujemy adresy naszych name serwerów.

Większość dostawców domen pozwala na przekierowanie domeny na inne name serwery. W przypadku nazwa.pl możemy je dodać w zakładce ‘Zewnętrzne serwery DNS’:

Po zaktualizowaniu DNS’ów możemy sprawdzić czy operacja się udała uruchamiając aplikacje host na ubuntu.

host -t NS k8s.com.pl

 

Przechowywanie stanu klastra

Kops wymaga miejsca do przechowywania stanu klastra, który tworzy. Dokumentacja kopsa określa, że można ten stan przechowywać na dysku. Jednak w praktyce nie jest to na razie możliwe na AWS (link do issue). Niestety, opcja z ‘wiadrem’ na AWS (S3 bucket) nie jest najlepsza, jeśli używamy darmowych zasobów na AWS (kops is not AWS free tier friendly), ponieważ kops dosyć szybko wysyca ten darmowy zasób, więc warto to monitorować. Na ten moment, jednak opcja z s3 jako magazyn dla stanu klastra to jedyna opcja.

Wyszukujemy w konsoli AWS usługi o nazwie s3. (https://s3.console.aws.amazon.com)

Klikamy Create bucket i wpisujemy nazwę naszego wiaderka – nie ma ona większego znaczenia. W kroku Set permissions zostawiamy zaznaczoną opcję `Block all public access`.

 

Tworzenie klastra

Przed uruchomieniem głównej komendy z tego wpisu chciałbym poruszyć kwestie wysokiej dostępności naszego klastra. W zależności od celów naszego klastra, czy będzie to klaster produkcyjny, czy testowy możemy w różny sposób podchodzić do zagadnienia wysokiej dostępności. Jeśli tworzymy klaster produkcyjny, warto stworzyć minimalnie 3 węzły typu master w osobnych strefach dostępności (availability zone). Dlaczego? Wewnętrznie kubernetes do przechowywania swojego stanu używa rozproszonej bazy danych etcd. Z kolei etcd by wykonać operacje na swoich danych potrzebuje otrzymać zgodę na wykonanie tej operacji od większości  swoich węzłów. Używa do tego algorytmu o nazwie Raft. Pokrótce: Raft, by osiągnąć konsensus potrzebuje (N/2)+1 dostępnych węzłów, gdzie N to liczba wszystkich węzłów typu master. Dlatego minimalna ilość węzłów master to 3, ponieważ  podstawiając pod powyższy wzór 2 węzły –  2/2 + 1 = 2. Oznacza to, że kubernetes będzie potrzebował 2 węzłów do poprawnego działania. W przypadku awarii jednego z węzłów kubernetes, a konkretnie etcd nie będzie w stanie wykonywać operacji. Dodatkowo AWS, nie posiada w każdym regionie 3 stref dostępności. Dla przykładu: we Frankfurcie (eu-central-1) istnieją tylko 2 takie strefy. Warto o tym pamiętać, ponieważ przy 3 węzłach master, 2 z nich będą znajdować się w jednej strefie. W przypadku awarii tej strefy tracimy możliwość modyfikacji naszego klastra, ponieważ stracimy 2 z 3 naszych masterów.

Dodatkowo, przy produkcyjnym klastrze warto zadbać o bezpieczeństwo. Polecam do tego dwa źródła: Kops security oraz  kubernetes security best practice. Na potrzeby tego wpisu jednak stworzę klaster dla środowiska testowego. Następująca komenda stworzy nam klaster w chmurze AWS:

kops create cluster           
     --master-count 3        
     --master-size=t2.micro        
     --node-count 2       
     --node-size=t2.micro  
     --zones eu-central-1a,eu-central-1b       
     --state "s3://kops.k8s.com.pl"       
     --name k8s.com.pl     
     --yes
  • `master-count` – ilość węzłów master. Liczba ta musi być nieparzysta
  •  master-size – wielkość maszyny wirtualnej dla węzła master
  • `node-count` – ilość węzłów aplikacyjnych
  • `node-size` – wielkość maszyny dla węzła aplikacyjnego
  • `zones` – strefy dostępności, w jakich mają znajdować się węzły. Kops postara się stworzyć maszyny równomiernie w każdej z podanych stref
  • state –  źródło stanu dla kopsa. `kops.k8s.com.pl` jest nazwą mojego wiaderka w s3. W przyszłości tutaj być może będzie można użyć pliku na dysku.
  • `name` – domena dla klastra. Przy braku domeny możemy podać domenę z końcówką k8s.local. Na przykład: mycluster.k8s.local.
  • yes – Zacznij tworzenie klastra. Jeśli nie określimy tej flagi, dostaniemy w konsoli przegląd tego, co zostanie stworzone poprzez kops w AWS.

W zależności od wielkości klastra jego tworzenie może potrwać od kilku do kilkunastu minut. Większe klastry wymagają akceptacji od AWS.

By sprawdzić czy klaster jest już gotowy, możemy uruchomić następującą komendę

kops validate cluster --state "s3://kops.k8s.com.pl" --name k8s.com.pl

 

Testowy deploy

Po stworzeniu klastra spróbujmy przeprowadzić prosty deploy. Dla testu stworzyłem  aplikację używając szablonu ASP.NET Core MVC zmieniając jej tylko stronę główną. Następnie opublikowałem obraz aplikacji w publicznym repozytorium dockerhub. Do deployu potrzebne nam będą dwa pliki yaml – definicje pod’a i serwisu.

apiVersion: v1
kind: Service
metadata:
  name: itdepends-service
spec:
  ports:
  - port: 80
    targetPort: itdepends-port
    protocol: TCP
  selector:
    app: itdepends-app
  type: LoadBalancer
apiVersion: v1
kind: Pod
metadata:
  name: itdepends
  labels:
    app: itdepends-app
spec:
  containers:
  - name: k8s-demo
    image: ddziub/k8s.test
    ports:
    - name: itdepends-port
      containerPort: 80

 

Pod jako kontener użyje obrazu z mojego publicznego repozytorium oraz wystawi go na porcie 80. Deploy serwisu stworzy zwykły loadbalancer ELB (także na porcie 80) w AWS oraz przekieruje ruch na kontener przy użyciu prostego label selectora. Obiekty tworzymy przy użyciu kubectl.

kubectl create -f itdepends-app.yml
kubectl create -f itdepends-service.yml

Po uruchomieniu komend, by zweryfikować deploy możemy pobrać serwisy kubernetesa i skopiować zewnętrzny adres loadbalancera.

kubectl get svc

 

Po przejściu na ten adres ukazuje się nam nasza testowa aplikacja:

 

Udostępnienie aplikacji pod subdomeną

Udostępnijmy jeszcze naszą aplikację pod naszą domeną. W tym celu przechodzimy ponownie do usługi Route53 i wybieramy naszą hosted zone.

Następnie klikamy Create Record Set. W prawym rogu wpisujemy nazwę subdomeny (może to być wildcard *). Zaznaczamy, że jest to alias i wybieramy z listy nasz load balancer i kilkamy Create.

Po stworzeniu record setu jesteśmy w stanie dostać się do aplikacji poprzez nową subdomenę (czasami może to potrwać chwilę).

Usuwanie klastra

Jeśli nie chcemy już używać naszego klastra warto wyczyścić wszystkie zasoby. Można to zrobić pojedynczą komendą:

kops delete cluster --state "s3://kops.k8s.com.pl" --name k8s.com.pl

 

W następnej części tej serii zajmiemy się konfiguracją repozytorium i deployem aplikacji z użyciem menadżera pakietów dla kuberetesa – helm.