EDU::GAMF::Felhőalapú-szolgáltatások::IaC
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.