Timp de nefuncționare zero Implementare continuă Jenkins cu Terraform pe AWS

Publicat: 2022-03-11

În lumea de astăzi a internetului, unde literalmente totul trebuie să fie activ 24/7, fiabilitatea este cheia. Acest lucru se traduce printr-un timp de nefuncționare aproape de zero pentru site-urile dvs. web, evitând temuta pagină de eroare „Negăsit: 404” sau alte întreruperi ale serviciului în timp ce lansați cea mai nouă versiune.

Să presupunem că ați creat o nouă aplicație pentru clientul dvs. sau poate pentru dvs. și ați reușit să obțineți o bază de utilizatori bună care să-i placă aplicația dvs. Ați adunat feedback de la utilizatori și vă adresați dezvoltatorilor și le cereți să creeze noi funcții și să pregătească aplicația pentru implementare. Cu aceasta gata, puteți fie să opriți întreaga aplicație și să implementați noua versiune, fie să construiți o conductă de implementare CI/CD cu timp de nefuncționare zero, care ar face toată munca obositoare de a transmite utilizatorilor o nouă versiune fără intervenție manuală.

În acest articol, vom vorbi exact despre acesta din urmă, cum putem avea o conductă de implementare continuă a unei aplicații web cu trei niveluri construită în Node.js pe AWS Cloud folosind Terraform ca orchestrator de infrastructură. Vom folosi Jenkins pentru partea de implementare continuă și Bitbucket pentru a găzdui baza noastră de cod.

Depozitul de coduri

Vom folosi o aplicație web demonstrativă cu trei niveluri pentru care puteți găsi codul aici.

Repo conține cod atât pentru web, cât și pentru stratul API. Este o aplicație simplă în care modulul web apelează unul dintre punctele finale din stratul API care preia intern informații despre ora curentă din baza de date și revine la stratul web.

Structura repo este următoarea:

  • API: cod pentru stratul API
  • Web: cod pentru stratul web
  • Terraform: Cod pentru orchestrarea infrastructurii folosind Terraform
  • Jenkins: Cod pentru orchestrator de infrastructură pentru serverul Jenkins utilizat pentru conducta CI/CD.

Acum că înțelegem ce trebuie să implementăm, să discutăm despre lucrurile pe care trebuie să le facem pentru a implementa această aplicație pe AWS și apoi vom vorbi despre cum să facem acea parte a conductei CI/CD.

Imagini de coacere

Deoarece folosim Terraform pentru orchestrator de infrastructură, este cel mai logic să aveți imagini precoapte pentru fiecare nivel sau aplicație pe care doriți să o implementați. Și pentru asta, am folosi un alt produs de la Hashicorp, adică Packer.

Packer este un instrument open-source care ajută la construirea unei imagini Amazon Machine sau AMI, care va fi folosită pentru implementarea pe AWS. Poate fi folosit pentru a construi imagini pentru diferite platforme precum EC2, VirtualBox, VMware și altele.

Iată un fragment al modului în care fișierul de configurare Packer ( terraform/packer-ami-api.json ) este utilizat pentru a crea un AMI pentru stratul 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" } ] }

Și trebuie să rulați următoarea comandă pentru a crea AMI:

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

Vom rula această comandă din versiunea Jenkins mai târziu în acest articol. În mod similar, vom folosi fișierul de configurare Packer ( terraform/packer-ami-web.json ) și pentru stratul web.

Să trecem prin fișierul de configurare Packer de mai sus și să înțelegem ce încearcă să facă.

  1. După cum am menționat mai devreme, Packer poate fi folosit pentru a construi imagini pentru multe platforme și, deoarece implementăm aplicația noastră pe AWS, vom folosi generatorul „amazon-ebs”, deoarece acesta este cel mai ușor generator de început.
  2. A doua parte a configurației are o listă de furnizori care sunt mai mult ca scripturi sau blocuri de cod pe care le puteți folosi pentru a vă configura imaginea.
    • Pasul 1 rulează un shell provisioner pentru a crea un folder API și pentru a instala Node.js pe imagine folosind proprietatea inline , care este un set de comenzi pe care doriți să le executați.
    • Pasul 2 rulează un furnizor de fișiere pentru a copia codul sursă din folderul API în instanță.
    • Pasul 3 rulează din nou un shell provisioner, dar de data aceasta folosește o proprietate de script pentru a specifica un fișier (terraform/scripts/install_api_software.sh) cu comenzile care trebuie executate.
    • Pasul 4 copiază un fișier de configurare în instanța necesară pentru Cloudwatch, care este instalată în pasul următor.
    • Pasul 5 rulează un furnizor de shell pentru a instala agentul AWS Cloudwatch. Intrarea la această comandă ar fi fișierul de configurare copiat în pasul anterior. Vom vorbi despre Cloudwatch în detalii mai târziu în articol.

Deci, în esență, configurația Packer conține informații despre ce constructor doriți și apoi un set de furnizori pe care îi puteți defini în orice ordine, în funcție de modul în care doriți să vă configurați imaginea.

Configurarea unei implementări continue Jenkins

În continuare, vom analiza configurarea unui server Jenkins care va fi folosit pentru conducta noastră CI/CD. Vom folosi Terraform și AWS și pentru a configura acest lucru.

Codul Terraform pentru setarea Jenkins se află în folderul jenkins/setup . Să trecem prin câteva dintre lucrurile interesante despre această configurație.

  1. Acreditări AWS: puteți fie să furnizați ID-ul cheii de acces AWS și cheia de acces secretă furnizorului Terraform AWS ( instance.tf ) sau puteți oferi locația fișierului de acreditări proprietății shared_credentials_file din furnizorul AWS.
  2. Rolul IAM: Deoarece vom rula Packer și Terraform de pe serverul Jenkins, aceștia vor accesa S3, EC2, RDS, IAM, echilibrarea încărcăturii și serviciile de autoscaling pe AWS. Deci, fie furnizăm acreditările noastre pe Jenkins pentru Packer & Terraform pentru a accesa aceste servicii, fie putem crea un profil IAM ( iam.tf ), folosind care am crea o instanță Jenkins.
  3. Stare Terraform: Terraform trebuie să mențină starea infrastructurii undeva într-un fișier și, cu S3 ( backend.tf ), o puteți menține acolo, astfel încât să puteți colabora cu alți colegi și oricine se poate schimba și implementa de la starea este întreținut într-o locație îndepărtată.
  4. Perechea de chei publice/private: va trebui să încărcați cheia publică a perechii de chei împreună cu instanța, astfel încât să puteți trimite ssh în instanța Jenkins odată ce aceasta este activată. Am definit o resursă aws_key_pair ( key.tf ) în care specificați locația cheii publice folosind variabile Terraform.

Pași pentru configurarea Jenkins:

Pasul 1: Pentru a păstra starea la distanță a Terraform, ar trebui să creați manual o găleată în S3 care poate fi utilizată de Terraform. Acesta ar fi singurul pas făcut în afara Terraform. Asigurați-vă că rulați AWS configure înainte de a rula comanda de mai jos pentru a specifica acreditările dvs. AWS.

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

Pasul 2: Rulați terraform init . Aceasta va inițializa starea și o va configura pentru a fi stocată pe S3 și va descărca pluginul furnizorului AWS.

Pasul 3: Executați terraform apply . Aceasta va verifica tot codul Terraform și va crea un plan și va arăta câte resurse vor fi create după terminarea acestui pas.

Pasul 4: Tastați yes , iar apoi pasul anterior va începe să creeze toate resursele. După ce comanda se termină, veți obține adresa IP publică a serverului Jenkins.

Pasul 5: Ssh pe serverul Jenkins, folosind cheia privată. ubuntu este numele de utilizator implicit pentru instanțele susținute de AWS EBS. Utilizați adresa IP returnată de comanda terraform apply .

 ssh -i mykey [email protected]

Pasul 6: Porniți interfața web Jenkins accesând http://34.245.4.73:8080 . Parola poate fi găsită la /var/lib/jenkins/secrets/initialAdminPassword .

Pasul 7: Alegeți „Instalați pluginuri sugerate” și creați un utilizator administrator pentru Jenkins.

Setarea conductei CI între Jenkins și Bitbucket

  1. Pentru aceasta, trebuie să instalăm pluginul Bitbucket în Jenkins. Accesați Manage Jenkins → Manage Plugins și din Plugin-uri disponibile instalați pluginul Bitbucket.
  2. În partea repo Bitbucket, accesați Setări → Webhooks , adăugați un nou webhook. Acest cârlig va trimite toate modificările din depozit către Jenkins și asta va declanșa conductele.
    Adăugarea unui webhook la implementarea continuă a Jenkins prin Bitbucker

Conducta Jenkins pentru a coace/construi imagini

  1. Următorul pas va fi crearea conductelor în Jenkins.
  2. Prima conductă va fi un proiect Freestyle care va fi folosit pentru a construi AMI-ul aplicației folosind Packer.
  3. Trebuie să specificați acreditările și adresa URL pentru depozitul dvs. Bitbucket.
    Adăugați acreditări la bitbucket
  4. Specificați declanșatorul Build.
    Configurarea declanșatorului de construcție
  5. Adăugați doi pași de construire, unul pentru construirea AMI pentru modulul de aplicație și alții pentru a construi AMI pentru modulul web.
    Adăugarea pașilor de construire a AMI
  6. Odată ce ați făcut acest lucru, puteți salva proiectul Jenkins și acum, când împingeți ceva în depozitul dvs. Bitbucket, va declanșa o nouă versiune în Jenkins care va crea AMI-ul și va împinge un fișier Terraform care conține numărul AMI al acelei imagini în Bucket S3 pe care îl puteți vedea din ultimele două rânduri în pasul de construire.
 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 pentru a declanșa scriptul Terraform

Acum că avem AMI-urile pentru modulele API și web, vom declanșa o compilare pentru a rula codul Terraform pentru configurarea întregii aplicații și mai târziu vom trece prin componentele din codul Terraform, ceea ce face ca această conductă să implementeze modificările fără timp de nefuncționare a serviciului.

  1. Creăm un alt proiect Jenkins freestyle, nodejs-terraform , care ar rula codul Terraform pentru a implementa aplicația.
  2. Vom crea mai întâi o autentificare de tip „text secret” în domeniul acreditărilor globale, care va fi folosită ca intrare în scriptul Terraform. Deoarece nu dorim să codificăm parola pentru serviciul RDS în Terraform și Git, trecem acea proprietate folosind acreditările Jenkins.
    crearea unui secret pentru utilizare cu Terraform ci cd
  3. Trebuie să definiți acreditările și URL-ul similar cu celălalt proiect.
  4. În secțiunea de declanșare a construirii, vom lega acest proiect cu celălalt astfel încât acest proiect să înceapă când cel anterior este terminat.
    Leagă proiecte între ele
  5. Apoi am putea configura acreditările pe care le-am adăugat mai devreme la proiect folosind legături, astfel încât să fie disponibile în pasul de construire.
    Configurarea legăturilor
  6. Acum suntem gata să adăugăm un pas de construire, care va descărca fișierele script Terraform ( amivar_api.tf și amivar_web.tf ) care au fost încărcate în S3 de către proiectul anterior și apoi rulează codul Terraform pentru a construi întreaga aplicație pe AWS.
    Adăugarea scriptului de construcție

Dacă totul este configurat corect, acum, dacă împingeți orice cod în depozitul dvs. Bitbucket, ar trebui să declanșeze primul proiect Jenkins urmat de al doilea și ar trebui să aveți aplicația implementată în AWS.

Configurare Terraform Zero Downtime pentru AWS

Acum să discutăm despre ce este în codul Terraform care face ca această conductă să implementeze codul fără timp de nefuncționare.

Primul lucru este că Terraform oferă aceste blocuri de configurare a ciclului de viață pentru resurse în cadrul cărora aveți opțiunea create_before_destroy ca flag, ceea ce înseamnă literalmente că Terraform ar trebui să creeze o nouă resursă de același tip înainte de a distruge resursa curentă.

Acum exploatăm această caracteristică în resursele aws_autoscaling_group și aws_launch_configuration . Deci, aws_launch_configuration configurează ce tip de instanță EC2 ar trebui să fie furnizată și modul în care instalăm software-ul pe acea instanță, iar resursa aws_autoscaling_group oferă un grup de autoscaling AWS.

O captură interesantă aici este că toate resursele din Terraform ar trebui să aibă o combinație unică de nume și tip. Deci, dacă nu aveți un nume diferit pentru noul aws_autoscaling_group și aws_launch_configuration , nu va fi posibil să îl distrugeți pe cel actual.

Terraform gestionează această constrângere furnizând o proprietate name_prefix resursei aws_launch_configuration . Odată ce această proprietate este definită, Terraform va adăuga un sufix unic la toate resursele aws_launch_configuration și apoi puteți utiliza acel nume unic pentru a crea o resursă aws_autoscaling_group .

Puteți verifica codul pentru toate cele de mai sus în 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 } }

Iar cea de-a doua provocare cu implementări fără timpi de nefuncționare este să vă asigurați că noua dvs. implementare este gata să înceapă să primească cererea. Doar implementarea și pornirea unei noi instanțe EC2 nu este suficientă în unele situații.

Pentru a rezolva această problemă, aws_launch_configuration are o proprietate user_data care acceptă proprietatea nativă AWS autoscaling user_data folosind care puteți trece orice script pe care doriți să îl rulați la pornirea unor instanțe noi ca parte a grupului de autoscaling. În exemplul nostru, urmărim jurnalul serverului de aplicații și așteptăm ca mesajul de pornire să fie acolo. Puteți, de asemenea, să verificați serverul HTTP și să vedeți când sunt activi.

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

Pe lângă aceasta, puteți activa și o verificare ELB la nivelul de resurse aws_autoscaling_group , care se va asigura că noua instanță a fost adăugată pentru a trece verificarea ELB înainte ca Terraform să distrugă instanțele vechi. Așa arată verificarea ELB pentru stratul API; se verifică dacă punctul final /api/status returnează succesul.

 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" } }

Rezumat și pașii următori

Așadar, acest lucru ne duce la sfârșitul acestui articol; sperăm că, până acum, fie aveți deja aplicația implementată și rulează cu o conductă CI/CD fără timpi de nefuncționare, folosind o implementare Jenkins și cele mai bune practici Terraform, fie vă simțiți puțin mai confortabil să explorezi acest teritoriu și să faci ca implementările tale să necesite cât mai puțină intervenție manuală. posibil.

În acest articol, strategia de implementare utilizată se numește implementare albastru-verde în care avem o instalare curentă (albastru) care primește trafic live în timp ce implementăm și testăm noua versiune (verde) și apoi le înlocuim odată ce noua versiune este totul gata. În afară de această strategie, există și alte modalități de a implementa aplicația dvs., care este explicată frumos în acest articol, Introducere în strategiile de implementare. Adaptarea unei alte strategii este acum la fel de simplă ca și configurarea conductei tale Jenkins.

De asemenea, în acest articol, am presupus că toate noile modificări ale API, web și straturile de date sunt compatibile, așa că nu trebuie să vă faceți griji că noua versiune vorbește cu o versiune mai veche. Dar, în realitate, s-ar putea să nu fie întotdeauna cazul. Pentru a rezolva această problemă, în timp ce proiectați noua versiune/funcții, gândiți-vă întotdeauna la nivelul de compatibilitate inversă, altfel va trebui să vă modificați implementările pentru a gestiona și această situație.

Testarea integrării este ceva care lipsește și din această conductă de implementare. Deoarece nu doriți ca nimic să fie eliberat utilizatorului final fără a fi testat, este cu siguranță ceva de reținut atunci când vine timpul să aplicați aceste strategii propriilor proiecte.

Dacă sunteți interesat să aflați mai multe despre cum funcționează Terraform și despre cum puteți implementa în AWS folosind tehnologia, vă recomand Terraform AWS Cloud: Sane Infrastructure Management , unde colegul Toptaler Radoslaw Szalski explică Terraform și vă arată pașii necesari pentru a configura un multiplu. -Configurare Terraform pregătită pentru mediu și producție pentru o echipă

Înrudit : Terraform vs. CloudFormation: Ghidul definitiv