EDU::GAMF::Felhőalapú-szolgáltatások::IaC

Innen: Farkas Attila Wiki
Ugrás a navigációhoz Ugrás a kereséshez

Mi az az Infrastructure as Code (IaC)?

Eddig a gyakorlatok során mindent kézzel, a grafikus felületen keresztül hoztunk létre - EC2 példányt, VPC-t, RDS-t, Security Groupot stb. Ez tanulási célra kiváló, mert látjuk, hogy pontosan mi történik, de éles környezetben rengeteg problémát okoz:

  • Nem reprodukálható megbízhatóan (két ember, két kattintgatás - könnyen eltérő eredmény)
  • Nincs verziókezelve, nem látszik a történet, ki mit miért módosított
  • Nincs automatikusan dokumentálva - a konfiguráció "a fejekben" és a felületen él
  • Nehéz gyorsan, azonos módon többször újra felépíteni (pl. teszt- és éles környezet)

Az Infrastructure as Code lényege, hogy az infrastruktúrát (és sokszor a rajta futó szolgáltatások konfigurációját is) szöveges, verziókezelhető fájlokban írjuk le, amit aztán eszközök automatikusan alkalmaznak. Ezen a gyakorlaton három, egymást kiegészítő - és a piacon igen elterjedt - eszközzel ismerkedünk meg:

Eszköz Feladata Egy mondatban
Terraform Infrastruktúra létrehozása (provisioning) "Milyen gépek/hálózatok/szolgáltatások legyenek?"
Ansible Már létező szerverek konfigurálása "Mi fusson/legyen telepítve a gépeken?"
Jenkins A folyamat automatizálása (CI/CD) "Mikor és hogyan fusson le mindez magától?"

A három eszköz tipikusan egy láncban dolgozik együtt: Jenkins elindítja a folyamatot (pl. egy git push hatására), ami meghívja a Terraform-ot, hogy felépítse/frissítse az infrastruktúrát, majd az Ansible konfigurálja fel és telepíti az alkalmazást a friss szerverekre.

Terraform

Mi az a Terraform?

A HashiCorp által fejlesztett, nyílt forráskódú IaC eszköz. Deklaratív nyelven (HCL - HashiCorp Configuration Language) írjuk le a kívánt végállapotot ("legyen egy ilyen EC2 példányom, ekkora Security Grouppal"), és a Terraform kitalálja, milyen API-hívásokkal éri ezt el - beleértve azt is, ha valamit módosítani vagy törölni kell egy korábbiállapothoz képest.

Telepítés

A Terraform nincs benne az alapértelmezett Ubuntu/Debian csomagtárolókban, a HashiCorp hivatalos tárolóját kell hozzáadnunk:

apt update && apt install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
  | tee /etc/apt/sources.list.d/hashicorp.list
apt update && apt install terraform -y
terraform -version

AWS hitelesítés

A Terraformnak ugyanazokra a hozzáférési kulcsokra van szüksége, mint korábban az AWSCLI-nek (lásd az AWS oldalon). Két lehetőségünk van:

  • Ha már lefuttattuk az aws configure parancsot, a Terraform automatikusan megtalálja azt
  • Vagy környezeti változóként is megadhatjuk:
export AWS_ACCESS_KEY_ID="ide_jön_az_access_key"
export AWS_SECRET_ACCESS_KEY="ide_jön_a_secret_key"
export AWS_DEFAULT_REGION="eu-west-1"
Figyelem! Soha ne írjuk a hozzáférési kulcsokat közvetlenül a .tf fájlokba, és ne kerüljenek verziókezelőbe (git)! Használjunk környezeti változót, vagy az AWS CLI által létrehozott hitelesítési fájlt.

Alapfogalmak

  • Provider: melyik szolgáltatóval (AWS, Azure, GCP stb.) és hogyan kommunikáljon a Terraform
  • Resource: egy konkrét, létrehozandó/kezelendő erőforrás (pl. egy EC2 példány)
  • Variable: paraméterezhető bemeneti érték (pl. instance típusa, régió)
  • Output: a végrehajtás után visszaadott érték (pl. a létrehozott szerver publikus IP-je)
  • State: a Terraform saját nyilvántartása arról, hogy legutóbb milyen állapotot hozott létre - ez alapján tudja kiszámolni, hogy legközelebb mit kell módosítania

Gyakorlati példa: EC2 + Security Group

Hozzunk létre egy main.tf fájlt, ami a korábban már megismert módon (lásd EC2 gyakorlat) indít egy t2.micro szervert, rajta egy Security Grouppal, ami engedi a SSH és HTTP forgalmat:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-1"
}

variable "neptun" {
  description = "Neptun kód, az erőforrások elnevezéséhez"
  type        = string
  default     = "neptun"
}

resource "aws_security_group" "web" {
  name        = "${var.neptun}-sg"
  description = "SSH + HTTP engedelyezese"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.neptun}-sg"
  }
}

resource "aws_instance" "web" {
  ami                    = "ami-0c1c30571d2dae5c9" # Ubuntu 22.04 LTS, eu-west-1
  instance_type          = "t2.micro"
  key_name               = "test" # a korabban letrehozott kulcspar neve
  vpc_security_group_ids = [aws_security_group.web.id]

  tags = {
    Name = "${var.neptun}-web"
  }
}

output "public_ip" {
  value = aws_instance.web.public_ip
}
Figyelem! Az AMI azonosító régiónként és időszakonként változik - mindig ellenőrizzük a konzolon (EC2 → Launch instance → AMI kiválasztás), hogy az adott régióban érvényes-e még az azonosító, mielőtt bemásoljuk.

A munkafolyamat: init / plan / apply / destroy

terraform init      # letölti a szükséges providereket, előkészíti a munkakönyvtárat - csak egyszer kell (vagy provider váltáskor)
terraform plan       # megmutatja, MIT fog csinálni, anélkül hogy bármit is végrehajtana - mindig nézzük át!
terraform apply       # végrehajtja a terveket - rákérdez, hogy biztosan folytassuk-e (yes-szel hagyjuk jóvá)
terraform destroy     # törli az összes, ebben a konfigurációban létrehozott erőforrást
Figyelem, ez a legfontosabb szabály a féléves munkához! Amikor végeztünk egy gyakorlattal, futtassunk terraform destroy-t, különben a létrehozott erőforrások tovább futnak és fizetési kötelezettséget okozhatnak a free tier keretein túl.

A state fájl

A terraform apply lefuttatása után megjelenik egy terraform.tfstate nevű fájl a könyvtárban - ez tárolja, hogy pontosan mit hozott létre a Terraform. Ez a fájl bizalmas adatokat is tartalmazhat (pl. jelszavakat, ha voltak a konfigurációban), ezért:

  • Soha ne kerüljön git-be (vegyük fel a .gitignore-ba)
  • Csapatmunkánál érdemes távoli tárolást használni (pl. S3 bucket, amit korábban már megismertünk), hogy mindenki ugyanazt az állapotot lássa

Ansible

Mi az az Ansible, és miben más, mint a Terraform?

Amíg a Terraform azzal foglalkozik, hogy milyen gépek létezzenek, addig az Ansible azzal, hogy mi legyen rajtuk telepítve/beállítva. Az Ansible agentless: nem kell külön ügynök-szoftvert telepíteni a célgépekre, elég, ha van rajtuk SSH szerver (amit eddig is használtunk a gépekhez való csatlakozáshoz) és Python.

Telepítés

apt update && apt install ansible -y
ansible --version

Inventory - kikhez csatlakozzon?

Az Ansible egy úgynevezett inventory fájlból tudja, mely szerverekkel dolgozzon. Hozzunk létre egy inventory.ini fájlt, a korábban megismert SSH kulcsot és felhasználónevet felhasználva (lásd itt):

[webservers]
web1 ansible_host=3.252.168.207 ansible_user=ubuntu ansible_ssh_private_key_file=~/test.pem

Ellenőrizzük, hogy elérjük-e a gépet:

ansible -i inventory.ini webservers -m ping

Ad-hoc parancsok

Egyszeri feladatokhoz nem kell playbookot írnunk, elég egy modult megneveznünk:

# csomaglista frissítése minden webservers csoportba tartozó gépen
ansible -i inventory.ini webservers -m apt -a "update_cache=yes" --become

# egy adott parancs lefuttatása
ansible -i inventory.ini webservers -m shell -a "uptime"
A --become kapcsoló jelenti a sudo-s (root) jogosultsággal történő végrehajtást, hasonlóan ahhoz, mintha sudo-t írnánk a parancs elé.

Playbook - a korábbi telepítő script Ansible-lel

Emlékezzünk vissza: a gyakorlat elején egy bash scripttel (UI indítás, lásd az AWS oldalon) telepítettük fel a weboldalunkat egy EC2-re. Ennek két nagy hátránya volt: csak egyszer, az EC2 indításakor futott le (User data), és nem volt idempotens - ha kétszer lefuttatnánk, hibákat okozna (pl. már létező mappa létrehozása). Nézzük meg, hogyan váltja ki ugyanezt egy Ansible playbook, amit bármikor, bármennyiszer újra lefuttathatunk anélkül, hogy kárt tenne:

---
- name: Weboldal UI telepítése
  hosts: webservers
  become: yes
  vars:
    neptun: "neptun"
    api_lb_domain: "localhost"

  tasks:
    - name: Csomaglista frissítése
      apt:
        update_cache: yes

    - name: Szükséges csomagok telepítése
      apt:
        name:
          - apache2
          - libapache2-mod-php8.3
          - php8.3
          - php8.3-curl
          - unzip
        state: present

    - name: Weboldal letöltése és kicsomagolása
      unarchive:
        src: https://wiki.farkas-attila.hu/images/c/c7/Weboldal.zip
        dest: /tmp/
        remote_src: yes

    - name: Célkönyvtár létrehozása
      file:
        path: "/srv/{{ neptun }}"
        state: directory

    - name: <ip> csere a tényleges API domain-re és fájlok elhelyezése
      copy:
        src: "/tmp/{{ item.src }}"
        dest: "/srv/{{ neptun }}/{{ item.dest }}"
        remote_src: yes
      loop:
        - { src: "ui.php", dest: "index.php" }
        - { src: "ui_newproduct.php", dest: "newproduct.php" }

    - name: <ip> tag lecserélése a tényleges API domain-re
      replace:
        path: "/srv/{{ neptun }}/{{ item }}"
        regexp: "<ip>"
        replace: "{{ api_lb_domain }}"
      loop:
        - index.php
        - newproduct.php

    - name: Apache virtualhost beállítása
      template:
        src: templates/000-default.conf.j2
        dest: /etc/apache2/sites-enabled/000-default.conf
      notify: Apache ujraindul

  handlers:
    - name: Apache ujraindul
      service:
        name: apache2
        state: restarted
Figyeljük meg az idempotencia elvét minden lépésnél: az apt modul csak akkor telepít, ha a csomag még nincs fent; a file modul csak akkor hoz létre könyvtárat, ha az még nem létezik. Ha kétszer futtatjuk le a playbookot, a második futás nem fog hibázni, csak megállapítja, hogy "már minden rendben van" (ez az úgynevezett changed=0 állapot).

A playbook futtatása:

ansible-playbook -i inventory.ini deploy-ui.yml

Jenkins

Mi az a Jenkins?

A Jenkins egy nyílt forráskódú CI/CD (Continuous Integration / Continuous Deployment) automatizáló szerver: az ő feladata, hogy a fejlesztői munka (pl. egy git push) hatására automatikusan lefuttassa a szükséges lépéseket - tesztelés, build, majd akár a Terraform és Ansible meghívása is, hogy a legfrissebb kód éles környezetbe kerüljön emberi beavatkozás nélkül.

Telepítés Docker konténerben

Mivel a félév második fele a Dockerről szól, a Jenkins telepítését is ezen keresztül végezzük el - így nem szennyezzük a hoszt rendszert, és könnyen el is távolítható:

docker volume create jenkins_home
docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts
A 8080-as port a webes admin felület, az 50000-es port pedig azoknak az esetleges elosztott build-ügynököknek (agent) van fenntartva, amik egy másik gépről csatlakoznának a Jenkins szerverhez.

A kezdeti admin jelszót a konténer belsejéből kell kiolvasnunk:

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Ezt beillesztve a http://<szerver_ip>:8080 címen elérhető telepítő varázslóba, majd a javasolt beépülők (plugins) telepítésével elkészül az alapkonfiguráció.

Pipeline alapfogalmak

A Jenkins munkafolyamatait Jenkinsfile néven, a projekt gyökerében elhelyezett, Groovy szintaxisú fájlban írjuk le (tehát ez a fájl is git-be kerül a projekt kódjával együtt - önmagában is egyfajta "code" a CI/CD folyamatból):

  • pipeline: a teljes folyamatot összefogó blokk
  • stage: egy-egy logikai szakasz (pl. "Build", "Terraform Apply", "Ansible Deploy") - ezek jelennek meg névvel a Jenkins felületén
  • steps: az adott szakaszban ténylegesen végrehajtandó parancsok

Gyakorlati példa: Terraform + Ansible pipeline

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/pelda/gamf-projekt.git'
            }
        }

        stage('Terraform Init') {
            steps {
                sh 'terraform init'
            }
        }

        stage('Terraform Apply') {
            steps {
                sh 'terraform apply -auto-approve'
            }
        }

        stage('Ansible Deploy') {
            steps {
                sh 'ansible-playbook -i inventory.ini deploy-ui.yml'
            }
        }
    }

    post {
        failure {
            echo 'A pipeline elszállt - ellenőrizzük a logokat!'
        }
    }
}
Figyelem! Ahhoz, hogy a fenti sh lépések ténylegesen működjenek, a Terraform-ot és az Ansible-t is telepíteni kell magába a Jenkins konténerbe (vagy egy erre felkészített, saját Dockerfile-ból épített Jenkins image-be - gondoljunk vissza a Docker fejezetre, pontosan ez a image készítés egyik tipikus, valós felhasználási módja).

Összefoglalás - hogyan áll össze a lánc?

Lépés Eszköz Mit csinál
1 Fejlesztő git push a kódra
2 Jenkins Észleli a változást, elindítja a pipeline-t
3 Terraform Felépíti/frissíti az infrastruktúrát (VPC, EC2, Security Group, RDS stb.)
4 Ansible Feltelepíti és konfigurálja az alkalmazást a friss szervereken
5 - Az alkalmazás elérhetővé válik, emberi kattintgatás nélkül

Ez a minta közvetlenül kapcsolódik a féléves projektfeladat követelményeihez is: a Terraformmal létrehozott VPC, EC2, ASG/LB, RDS mind beleszámít a projektkövetelmény-listába (lásd a főoldalon), csak éppen automatizált, verziókezelt, újra futtatható formában - ami jóval közelebb áll ahhoz, ahogy ma egy valós cégnél is üzemeltetik a felhős infrastruktúrát.

Jegyzetek

  • AWS
  • Docker
  • Előadás - lásd a Felhő ökoszisztéma fejezet IaC alfejezetét az elméleti háttérhez