Zero przestojów Jenkins Continuous Deployment z Terraform na AWS

Opublikowany: 2022-03-11

W dzisiejszym świecie internetu, w którym dosłownie wszystko musi działać 24 godziny na dobę, 7 dni w tygodniu, niezawodność ma kluczowe znaczenie. Przekłada się to na prawie zerowy czas przestoju Twoich stron internetowych, unikanie przerażającej strony błędu „Nie znaleziono: 404” lub innych zakłóceń usług podczas wdrażania najnowszej wersji.

Załóżmy, że zbudowałeś nową aplikację dla swojego klienta, a może dla siebie, i udało Ci się zdobyć dobrą bazę użytkowników, którzy lubią Twoją aplikację. Po zebraniu opinii użytkowników idziesz do swoich programistów i prosisz ich o zbudowanie nowych funkcji i przygotowanie aplikacji do wdrożenia. Mając to gotowe, możesz zatrzymać całą aplikację i wdrożyć nową wersję lub zbudować potok wdrażania CI/CD bez przestojów, który wykonałby całą żmudną pracę polegającą na wypychaniu nowego wydania do użytkowników bez ręcznej interwencji.

W tym artykule omówimy dokładnie to drugie, w jaki sposób możemy mieć ciągły potok wdrażania trójwarstwowej aplikacji internetowej zbudowanej w Node.js w chmurze AWS przy użyciu Terraform jako koordynatora infrastruktury. Będziemy używać Jenkins do części ciągłego wdrażania i Bitbucket do hostowania naszej bazy kodu.

Repozytorium kodu

Będziemy korzystać z demonstracyjnej trójwarstwowej aplikacji internetowej, której kod można znaleźć tutaj.

Repozytorium zawiera kod zarówno dla strony internetowej, jak i warstwy API. Jest to prosta aplikacja, w której moduł sieciowy wywołuje jeden z punktów końcowych w warstwie API, który wewnętrznie pobiera informacje o bieżącym czasie z bazy danych i powraca do warstwy sieciowej.

Struktura repozytorium jest następująca:

  • API: Kod dla warstwy API
  • Sieć: kod warstwy internetowej
  • Terraform: kod do aranżacji infrastruktury przy użyciu Terraform
  • Jenkins: kod programu Orchestrator infrastruktury dla serwera Jenkins używany dla potoku CI/CD.

Teraz, gdy rozumiemy, co musimy wdrożyć, omówmy, co musimy zrobić, aby wdrożyć tę aplikację na AWS, a następnie porozmawiamy o tym, jak zrobić tę część potoku CI/CD.

Pieczenie obrazów

Ponieważ używamy Terraform do programu Orchestrator infrastruktury, najbardziej sensowne jest posiadanie wstępnie przygotowanych obrazów dla każdej warstwy lub aplikacji, którą chcesz wdrożyć. A do tego używalibyśmy innego produktu firmy Hashicorp – czyli Packera.

Packer to narzędzie typu open source, które pomaga zbudować obraz maszyny Amazon lub AMI, który zostanie wykorzystany do wdrożenia na AWS. Może być używany do budowania obrazów dla różnych platform, takich jak EC2, VirtualBox, VMware i inne.

Oto fragment opisujący, jak plik konfiguracyjny Packera ( terraform/packer-ami-api.json ) jest używany do tworzenia AMI dla warstwy API.

 { "builders": [{ "type": "amazon-ebs", "region": "eu-west-1", "source_ami": "ami-844e0bf7", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "api-instance {{timestamp}}" }], "provisioners": [ { "type": "shell", "inline": ["mkdir api", "sudo apt-get update", "sudo apt-get -y install npm nodejs-legacy"], "pause_before": "10s" }, { "type": "file", "source" : "../api/", "destination" : "api" }, { "type": "shell", "inline": ["cd api", "npm install"], "pause_before": "10s" } ] }

Aby utworzyć AMI, musisz uruchomić następujące polecenie:

 packer build -machine-readable packer-ami-api.json

W dalszej części tego artykułu uruchomimy to polecenie z kompilacji Jenkins. W podobny sposób będziemy używać pliku konfiguracyjnego Packera ( terraform/packer-ami-web.json ) również dla warstwy internetowej.

Przejrzyjmy powyższy plik konfiguracyjny Packera i zrozummy, co próbuje zrobić.

  1. Jak wspomniano wcześniej, Packer może być używany do budowania obrazów dla wielu platform, a ponieważ wdrażamy naszą aplikację w AWS, będziemy używać kreatora „amazon-ebs”, ponieważ jest to najłatwiejszy kreator do rozpoczęcia.
  2. Druga część konfiguracji zawiera listę dostawców, które bardziej przypominają skrypty lub bloki kodu, których można użyć do skonfigurowania obrazu.
    • Krok 1 uruchamia dostawcę powłoki, aby utworzyć folder API i zainstalować Node.js na obrazie za pomocą właściwości inline , która jest zestawem poleceń, które chcesz uruchomić.
    • Krok 2 uruchamia dostawcę plików w celu skopiowania naszego kodu źródłowego z folderu API do instancji.
    • Krok 3 ponownie uruchamia dostawcę powłoki, ale tym razem używa właściwości skryptu do określenia pliku (terraform/scripts/install_api_software.sh) z poleceniami, które należy uruchomić.
    • Krok 4 kopiuje plik konfiguracyjny do instancji, która jest potrzebna dla Cloudwatch, która jest instalowana w następnym kroku.
    • Krok 5 uruchamia dostawcę powłoki, aby zainstalować agenta AWS Cloudwatch. Dane wejściowe do tego polecenia będzie plikiem konfiguracyjnym skopiowanym w poprzednim kroku. O Cloudwatch omówimy szczegółowo w dalszej części artykułu.

Zasadniczo konfiguracja Packera zawiera informacje o tym, jakiego konstruktora chcesz, a następnie zestaw dostawców, które możesz zdefiniować w dowolnej kolejności, w zależności od tego, jak chcesz skonfigurować obraz.

Konfigurowanie ciągłego wdrażania Jenkins

Następnie przyjrzymy się skonfigurowaniu serwera Jenkins, który będzie używany dla naszego potoku CI/CD. Będziemy używać Terraform i AWS również do tego.

Kod Terraform do ustawienia Jenkins znajduje się w folderze jenkins/setup . Omówmy kilka interesujących rzeczy związanych z tą konfiguracją.

  1. Poświadczenia AWS: możesz podać identyfikator klucza dostępu AWS i tajny klucz dostępu dostawcy Terraform AWS ( instance.tf ) lub możesz podać lokalizację pliku poświadczeń do właściwości shared_credentials_file w dostawcy AWS.
  2. Rola uprawnień: ponieważ będziemy uruchamiać Packer i Terraform z serwera Jenkins, będą mieli dostęp do usług S3, EC2, RDS, IAM, równoważenia obciążenia i autoskalowania w AWS. Dlatego albo podajemy nasze dane uwierzytelniające w Jenkins dla Packer & Terraform, aby uzyskać dostęp do tych usług, albo możemy utworzyć profil IAM ( iam.tf ), za pomocą którego utworzymy instancję Jenkins.
  3. Stan Terraform: Terraform musi utrzymywać stan infrastruktury gdzieś w pliku, a dzięki S3 ( backend.tf ) możesz po prostu go tam utrzymywać, dzięki czemu możesz współpracować z innymi współpracownikami, a każdy może zmieniać i wdrażać od stanu jest utrzymywana w zdalnej lokalizacji.
  4. Para kluczy publiczny/prywatny: Będziesz musiał przesłać klucz publiczny swojej pary kluczy wraz z instancją, aby móc ssh do instancji Jenkinsa po jej uruchomieniu. Zdefiniowaliśmy zasób aws_key_pair ( key.tf ), w którym określasz lokalizację swojego klucza publicznego za pomocą zmiennych Terraform.

Kroki konfiguracji Jenkinsa:

Krok 1: Aby zachować zdalny stan Terraform, musisz ręcznie utworzyć zasobnik w S3, który może być używany przez Terraform. Byłby to jedyny krok zrobiony poza Terraformem. Upewnij się, że uruchomiłeś AWS configure przed uruchomieniem poniższego polecenia, aby określić swoje poświadczenia AWS.

 aws s3api create-bucket --bucket node-aws-jenkins-terraform --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1

Krok 2: Uruchom terraform init . Spowoduje to zainicjowanie stanu i skonfigurowanie go do przechowywania w S3 oraz pobranie wtyczki dostawcy AWS.

Krok 3: Uruchom terraform apply . Spowoduje to sprawdzenie całego kodu Terraform i utworzenie planu oraz pokazanie, ile zasobów zostanie utworzonych po zakończeniu tego kroku.

Krok 4: Wpisz yes , a poprzedni krok rozpocznie tworzenie wszystkich zasobów. Po zakończeniu polecenia otrzymasz publiczny adres IP serwera Jenkins.

Krok 5: Połącz się z serwerem Jenkins, używając swojego klucza prywatnego. ubuntu to domyślna nazwa użytkownika dla instancji wspieranych przez AWS EBS. Użyj adresu IP zwróconego przez polecenie terraform apply .

 ssh -i mykey [email protected]

Krok 6: Uruchom internetowy interfejs użytkownika Jenkins, przechodząc do http://34.245.4.73:8080 . Hasło można znaleźć w /var/lib/jenkins/secrets/initialAdminPassword .

Krok 7: Wybierz „Zainstaluj sugerowane wtyczki” i utwórz użytkownika administratora dla Jenkins.

Ustalanie potoku CI między Jenkinsem a Bitbucket

  1. W tym celu musimy zainstalować wtyczkę Bitbucket w Jenkins. Przejdź do Zarządzaj Jenkinsem → Zarządzaj wtyczkami i z Dostępne wtyczki zainstaluj wtyczkę Bitbucket.
  2. Po stronie repozytorium Bitbucket przejdź do Ustawienia → Webhooki , dodaj nowego webhooka. Ten hak wyśle ​​​​wszystkie zmiany w repozytorium do Jenkinsa, co uruchomi potoki.
    Dodawanie webhooka do ciągłego wdrażania Jenkins za pośrednictwem Bitbucker

Jenkins Pipeline do pieczenia/tworzenia obrazów

  1. Kolejnym krokiem będzie stworzenie rurociągów w Jenkins.
  2. Pierwszym potoku będzie projekt Freestyle, który zostanie wykorzystany do zbudowania AMI aplikacji przy użyciu Packera.
  3. Musisz określić poświadczenia i adres URL swojego repozytorium Bitbucket.
    Dodaj dane logowania do bitbucket
  4. Określ wyzwalacz kompilacji.
    Konfigurowanie wyzwalacza kompilacji
  5. Dodaj dwa etapy kompilacji, jeden do kompilowania modułu AMI dla aplikacji, a drugi do kompilowania modułu AMI dla sieci Web.
    Dodawanie kroków kompilacji AMI
  6. Gdy to zrobisz, możesz zapisać projekt Jenkins, a teraz, gdy wypchniesz cokolwiek do repozytorium Bitbucket, uruchomi to nową kompilację w Jenkins, która utworzy AMI i wypchnie plik Terraform zawierający numer AMI tego obrazu do Wiadro S3, które widać z dwóch ostatnich wierszy na etapie budowania.
 echo 'variable "WEB_INSTANCE_AMI" { default = "'${AMI_ID_WEB}'" }' > amivar_web.tf aws s3 cp amivar_web.tf s3://node-aws-jenkins-terraform/amivar_web.tf

Jenkins Pipeline do uruchomienia skryptu Terraform

Teraz, gdy mamy AMI dla modułów API i sieci Web, uruchomimy kompilację, aby uruchomić kod Terraform w celu skonfigurowania całej aplikacji, a później przejdziemy przez komponenty w kodzie Terraform, co spowoduje, że ten potok wdroży zmiany z zerowym przestojem usługi.

  1. Tworzymy kolejny projekt Jenkins freestyle, nodejs-terraform , który będzie uruchamiał kod Terraform do wdrożenia aplikacji.
  2. Najpierw utworzymy poświadczenie typu „tajny tekst” w globalnej domenie poświadczeń, które będzie używane jako dane wejściowe do skryptu Terraform. Ponieważ nie chcemy na sztywno kodować hasła do usługi RDS w Terraform i Git, przekazujemy tę właściwość za pomocą poświadczeń Jenkins.
    tworzenie sekretu do użytku z Terraform ci cd
  3. Musisz zdefiniować poświadczenia i adres URL podobnie jak w innym projekcie.
  4. W sekcji wyzwalacza kompilacji połączymy ten projekt z drugim w taki sposób, aby ten projekt rozpoczął się po zakończeniu poprzedniego.
    Połącz projekty razem
  5. Następnie moglibyśmy skonfigurować poświadczenia, które dodaliśmy wcześniej do projektu za pomocą powiązań, aby były dostępne w kroku budowania.
    Konfiguracja powiązań
  6. Teraz jesteśmy gotowi do dodania kroku kompilacji, który pobierze pliki skryptów Terraform ( amivar_api.tf i amivar_web.tf ), które zostały przesłane do S3 przez poprzedni projekt, a następnie uruchomi kod Terraform w celu zbudowania całej aplikacji na AWS.
    Dodawanie skryptu kompilacji

Jeśli wszystko jest poprawnie skonfigurowane, teraz, jeśli wypchniesz dowolny kod do swojego repozytorium Bitbucket, powinno to wywołać pierwszy projekt Jenkins, a następnie drugi, a Twoja aplikacja powinna zostać wdrożona w AWS.

Konfiguracja Terraform Zero Downtime dla AWS

Teraz omówmy, co to jest w kodzie Terraform, które powoduje, że ten potok wdraża kod bez przestojów.

Pierwszą rzeczą jest to, że Terraform udostępnia te bloki konfiguracji cyklu życia dla zasobów, w których masz opcję create_before_destroy jako flagę, co dosłownie oznacza, że ​​Terraform powinien utworzyć nowy zasób tego samego typu przed zniszczeniem bieżącego zasobu.

Teraz wykorzystujemy tę funkcję w aws_autoscaling_group i aws_launch_configuration . Tak więc aws_launch_configuration konfiguruje typ instancji EC2 i sposób instalowania oprogramowania w tej instancji, a zasób aws_autoscaling_group udostępnia grupę autoskalowania AWS.

Ciekawym haczykiem jest to, że wszystkie zasoby w Terraform powinny mieć unikalną kombinację nazwy i typu. Więc jeśli nie masz innej nazwy dla nowej aws_autoscaling_group i aws_launch_configuration , nie będzie możliwe zniszczenie obecnej.

Terraform obsługuje to ograniczenie, udostępniając właściwość name_prefix aws_launch_configuration . Po zdefiniowaniu tej właściwości Terraform doda unikalny sufiks do wszystkich zasobów aws_launch_configuration , a następnie będzie można użyć tej unikatowej nazwy do utworzenia zasobu aws_autoscaling_group .

Możesz sprawdzić kod dla wszystkich powyższych w terraform/autoscaling-api.tf

 resource "aws_launch_configuration" "api-launchconfig" { name_prefix = "api-launchconfig-" image_ instance_type = "t2.micro" security_groups = ["${aws_security_group.api-instance.id}"] user_data = "${data.template_file.api-shell-script.rendered}" iam_instance_profile = "${aws_iam_instance_profile.CloudWatchAgentServerRole-instanceprofile.name}" connection { user = "${var.INSTANCE_USERNAME}" private_key = "${file("${var.PATH_TO_PRIVATE_KEY}")}" } lifecycle { create_before_destroy = true } } resource "aws_autoscaling_group" "api-autoscaling" { name = "${aws_launch_configuration.api-launchconfig.name}-asg" vpc_zone_identifier = ["${aws_subnet.main-public-1.id}"] launch_configuration = "${aws_launch_configuration.api-launchconfig.name}" min_size = 2 max_size = 2 health_check_grace_period = 300 health_check_type = "ELB" load_balancers = ["${aws_elb.api-elb.name}"] force_delete = true lifecycle { create_before_destroy = true } tag { key = "Name" value = "api ec2 instance" propagate_at_launch = true } }

Drugim wyzwaniem przy wdrożeniach bez przestojów jest upewnienie się, że nowe wdrożenie jest gotowe do otrzymywania żądania. Samo wdrożenie i uruchomienie nowej instancji EC2 w niektórych sytuacjach nie wystarczy.

Aby rozwiązać ten problem, aws_launch_configuration ma właściwość user_data , która obsługuje natywną właściwość user_data autoskalowania AWS, za pomocą której możesz przekazać dowolny skrypt, który chcesz uruchomić podczas uruchamiania nowych instancji w ramach grupy autoskalowania. W naszym przykładzie śledzimy dziennik serwera aplikacji i czekamy na pojawienie się komunikatu startowego. Możesz także sprawdzić serwer HTTP i zobaczyć, kiedy działają.

 until tail /var/log/syslog | grep 'node ./bin/www' > /dev/null; do sleep 5; done

Oprócz tego można również włączyć sprawdzanie ELB na poziomie zasobów aws_autoscaling_group , co upewni się, że nowe wystąpienie zostało dodane, aby przejść sprawdzanie ELB, zanim Terraform zniszczy stare wystąpienia. Tak wygląda sprawdzenie ELB dla warstwy API; sprawdza, czy punkt końcowy /api/status zwraca sukces.

 resource "aws_elb" "api-elb" { name = "api-elb" subnets = ["${aws_subnet.main-public-1.id}"] security_groups = ["${aws_security_group.elb-securitygroup.id}"] listener { instance_port = "${var.API_PORT}" instance_protocol = "http" lb_port = 80 lb_protocol = "http" } health_check { healthy_threshold = 2 unhealthy_threshold = 2 timeout = 3 target = "HTTP:${var.API_PORT}/api/status" interval = 30 } cross_zone_load_balancing = true connection_draining = true connection_draining_timeout = 400 tags { Name = "my-elb" } }

Podsumowanie i kolejne kroki

To prowadzi nas do końca tego artykułu; miejmy nadzieję, że do tej pory albo masz już wdrożoną aplikację i działasz z potokiem CI/CD z zerowym przestojem, korzystając z wdrożenia Jenkins i najlepszych praktyk Terraform, albo możesz nieco wygodniej eksplorować ten obszar i sprawić, by Twoje wdrożenia wymagały tak niewielkiej ręcznej interwencji, jak możliwy.

W tym artykule stosowana strategia wdrażania nazywa się wdrożeniem niebiesko-zielonym, w którym mamy bieżącą instalację (niebieska), która odbiera ruch na żywo podczas wdrażania i testowania nowej wersji (zielonej), a następnie zastępujemy je, gdy nowa wersja jest wszystko gotowe. Oprócz tej strategii istnieją inne sposoby wdrażania aplikacji, które są ładnie wyjaśnione w tym artykule, Wprowadzenie do strategii wdrażania. Dostosowanie innej strategii jest teraz tak proste, jak skonfigurowanie potoku Jenkins.

Ponadto w tym artykule założyłem, że wszystkie nowe zmiany w API, sieci i warstwach danych są kompatybilne, więc nie musisz się martwić, że nowa wersja będzie komunikować się ze starszą wersją. Ale w rzeczywistości nie zawsze tak jest. Aby rozwiązać ten problem, podczas projektowania nowego wydania/funkcji, zawsze myśl o warstwie kompatybilności wstecznej, w przeciwnym razie będziesz musiał dostosować swoje wdrożenia, aby poradzić sobie również z tą sytuacją.

Testowanie integracji jest również czymś, czego brakuje w tym potoku wdrażania. Ponieważ nie chcesz, aby cokolwiek zostało wydane użytkownikowi końcowemu bez testowania, zdecydowanie należy o tym pamiętać, gdy nadejdzie czas, aby zastosować te strategie we własnych projektach.

Jeśli chcesz dowiedzieć się więcej o tym, jak działa Terraform i jak możesz wdrożyć w AWS za pomocą tej technologii, polecam Terraform AWS Cloud: Sane Infrastructure Management , gdzie inny Toptaler Radosław Szalski wyjaśnia Terraform, a następnie pokazuje kroki potrzebne do skonfigurowania multi - środowiskowa i produkcyjna konfiguracja Terraform dla zespołu

Powiązane: Terraform vs. CloudFormation: The Definitive Guide