devops

Teljesen automatizált AI következtetések az AWS, Azure és Google Cloud szolgáltatásokkal a Pulumi segítségével

Forrás: pulumi.com 21 perc olvasás

Megosztás

Teljesen automatizált AI következtetések az AWS, Azure és Google Cloud szolgáltatásokkal a Pulumi segítségével

Összefoglalót olvas. A teljes tartalom itt érhető el: pulumi.com.

Az Ollama felhőalapú GPU-n való futtatása folyamatosan visszatérő téma számomra. Egy ideje írtam egy cikket arról, hogyan lehet nyílt forráskódú LLM-eket futtatni egy AWS EC2 példányon Ollama és Pulumi segítségével, és a felépítés sohasem igazán változik: egy GPU példány, egy modellkiszolgáló és egy tűzfal szabály az előtérben. Az infrastruktúra kód formájában való kezelése elérte a célját azáltal, hogy az ilyen beállítások előre megjósolhatók és megismételhetők, az AI infrastruktúra sem kivétel. Egy GPU box, amely egy modellt szolgál ki, továbbra is egy virtuális gép, egy lemez és egy tűzfal szabály, amit úgy kell nyilván tartani, mint egyet. Thorsten Hans pontosan ezt a kérdést bontotta ki az Akamai blogbejegyzésében, amely a Teljesen Automatizált AI Infrastruktúrák Terraform és Akamai Cloud használatával címet viseli, ahol felállít egyetlen GPU példányt a Linode-on, telepíti a drivereket, futtatja az Ollama-t és letölti a modellt, manuális lépések nélkül a terraform apply után. Tetszett az elképzelés, ezért ez a bejegyzés ugyanazt az ötletet ülteti át Pulumi-ra és futtatja azt AWS, Azure és Google Cloud rendszerek között, nem csupán egyen. Az eredmény egy program formátum minden egyes felhőn: egyetlen pulumi up parancs felállít egy GPU boxot, amely telepíti a saját drivereit, futtatja az Ollama-t, és letölti a modellt manuális lépések nélkül, míg egyetlen pulumi destroy parancs visszaállítja. Közben eltávolítja azokat a két kulcsfontosságú elemet, amikre a Terraform verzió támaszkodik: egy statikus hozzáférési tokent, amely a környezeti változókban ül, és egy null_resource, amely egy shell ciklusban várja a modellt. Az első egy OIDC bejelentkezéssé alakul át egy Pulumi ESC környezetből, így nincsenek hosszú életű kulcsok sehol. A második kiderül, hogy nem is erőforrás. Amit építesz Eltávolítva a felhőspecifikus névhasználatot, minden verzió három dologból áll: egy GPU virtuális gépből, egy előtte álló tűzfalból, és egy cloud-init scriptből, amely egy üres Ubuntu boxot futó inferencia szerverré alakít. A modellkiszolgálás az Ollama-n fut, amely HTTP API-t szolgáltat a 11434-es porton, és a modellt a GPU memória között tartja a kérések között. flowchart LR Dev([A géped / curl]) -->|"HTTP :11434"| FW["Tűzfal / biztonsági csoport<br/>(engedélyezi a 11434-es portot, opcionálisan a 22-es portot)"] FW --> VM["GPU VM (Ubuntu 24.04)<br/>NVIDIA driver + Ollama"] VM -->|"GPU memóriában marad"| Model["qwen2.5:14b"] ESC["Pulumi ESC<br/>pulumi-idp/auth"] -.->|"OIDC bejelentkezés (rövid életű hitelesítő adatok)"| Pulumi["pulumi up"] Pulumi -.->|"AWS / Azure / GCP"| VM Összetevő Port Leírás GPU VM - Ubuntu 24.04 NVIDIA T4-osztályú GPU-val. Telepíti a drivert és az Ollama-t cloud-initből, majd letölti a modellt. Ollama 11434 Szolgáltatja a modellt HTTP API-n keresztül, és a GPU memóriában tartja a hívások között. Tűzfal / biztonsági csoport - Engedélyezi a bejövő 11434-es portot (és a 22-est, ha kéred). Nyitva a világ számára egy bemutatóra; zárd le a CIDR-t valami ténylegeshez. pulumi-idp/auth (ESC) - Egy környezet, amely egy OIDC bejelentkezés biztosít az AWS, Azure és GCP rendszerekhez. Nincs statikus kulcs a kódban vagy CI-ben. Egy részlet itt érdemes megállni, és elmagyarázza, miért lett a Pulumi verzió rövidebb, mint a Terraform verzió. Az Akamai projekt végén egy null_resource található, amely a curl ciklust futtatja a local-exec-ben, hogy blokkolja a terraform apply-t, amíg a modell letöltése be nem fejeződik. Ez nem infrastruktúra, hanem egy futási ellenőrzés, amely erőforrást jelképez. A Pulumi rendelkezik egy Command providerrel, amely lehetővé tenné, hogy ezt sorban újraalkosd, és ez a bejegyzés szándékosan nem teszi. A program provisionálja a boxot, a box saját maga tölti le a modellt, és a program kinyomtatja a végpontot. A modell letöltése befejeződött-e, az egy curl-lel megválaszolandó kérdés, nem pedig olyan kérdés, amelyre az IaC eszközödnek kellene várnia a telepítést, hogy megkérdezhesse. Előfeltételek Mielőtt elkezdenéd, győződj meg róla, hogy: Telepítetted és konfiguráltad a Pulumi CLI-t Van egy Pulumi Cloud fiókod (itt él az ESC és az OIDC) Van egy fiókod legalább az AWS, Azure vagy Google Cloud egyikén, az OIDC bizalom beállítva a Pulumi számára (erről később) Elég GPU kvótád van a cél régiódban egy T4-es példányhoz; egy friss fiók gyakran nulláról indul, amely a leggyakoribb oka annak, hogy egy első telepítés megbukik, szóval ellenőrizd, mielőtt futtatod a pulumi up parancsot Node.js 18+-as verzió a TypeScript programhoz curl, hogy beszélgegess az inferencia végponttal, miután az elindult. Ez a bejegyzés a qwen2.5:14b modellt szolgáltatja, ugyanazt, amit az Akamai cikk használ, mert a 4-bites kvantálás kényelmesen befér egyetlen 16 GB T4-re. A modell konfigurációs értéke egyetlen paraméter (model), így bármire cserélheted, amit az Ollama támogat; a GPU-t a modell memóriakövetelményeihez igazítsd, mert egy modell, amely túllép a VRAM-on, még fut, de a CPU-ra is ráesterem. Hitelesítő adatok másolás és beillesztés nélkül A Terraform verzió az egyetlen módon hitelesíti, ahogyan egy egyfelhős demo a legjobban működhet: kibocsátasz egy személyes hozzáférési tokent, exportálod LINODE_TOKEN-ként, és a provider ebből olvas. Működik, de ez a token hosszú életű, a shell történetedben és a CI titkosítók között ül, és felhőnként egyet bocsátasz ki. Három felhőre ez három statikus kulcsot jelent, amiket forgatni és aggódni kell miattuk. A Pulumi ESC (Környezetek, Titkok és Konfiguráció) mindezt OIDC bejelentkezéssel helyettesíti. Az ötlet: a felhőkulcs tárolása helyett egy bizalmi kapcsolatot tárolsz. Telepítéskor az ESC egy rövid életű OIDC tokent mutat be az AWS, Azure vagy Google Cloud rendszereken, és mindegyik visszaad ideiglenes hitelesítő adatokat, amelyek egy általad ellenőrzött szerepkörhöz vannak kötve. Semmi hosszú életű sohasem kerül írásban. Ezt a huzalozást egy környezetben tartom, a pulumi-idp/auth néven, és minden stack importálja. Íme az egész: # pulumi-idp/auth: egy ESC környezet, amely az OIDC bejelentkezést közvetíti mindhárom # felhőbe. Minden stack importálja. Nincs statikus felhőkulcs itt vagy máshol. értékek: aws: bejelentkezés: fn::open::aws-login: oidc: roleArn: arn:aws:iam::123456789012:role/pulumi-esc sessionName: pulumi-esc azure: bejelentkezés: fn::open::azure-login: clientId: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee tenantId: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee subscriptionId: /subscriptions/00000000-0000-0000-0000-000000000000 oidc: true gcp: bejelentkezés: fn::open::gcp-login: project: 123456789012 # numerikus projekt szám, nem a projekt azonosító oidc: workloadPoolId: pulumi-esc providerId: pulumi-esc serviceAccount: pulumi-esc@my-project.iam.gserviceaccount.com környezeti változók: # AWS: a Pulumi AWS provider, az AWS SDK-k és az aws CLI olvassa AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId} AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey} AWS_SESSION_TOKEN: ${aws.login.sessionToken} # Azure: az azure-native provider olvassa ARM_USE_OIDC: "true" ARM_CLIENT_ID: ${azure.login.clientId} ARM_TENANT_ID: ${azure.login.tenantId} ARM_SUBSCRIPTION_ID: ${azure.login.subscriptionId} ARM_OIDC_TOKEN: ${azure.login.oidc.token} # Google Cloud: a Pulumi Google Cloud provider olvassa GOOGLE_CLOUD_PROJECT: ${gcp.login.project} GOOGLE_OAUTH_ACCESS_TOKEN: ${gcp.login.accessToken} Csak azokat a blokkokat kell megadni, amelyeket a tényleges telepítésedhez használsz. Ennek a környezetnek a megnyitása lehetővé teszi, hogy az ESC minden felsorolt ​​felhőbe élő OIDC bejelentkezést végezzen, így vágd le azt, amit használsz, vagy tartsd meg mindháromban, ha úgy, mint én, ugrálsz köztük. Minden felhőnek egy titkos bizalmi beállításra van szüksége ahhoz, hogy ez a bejelentkezés egyáltalán engedélyezett legyen: egy IAM OIDC azonosító szolgáltató és szerep az AWS-en, egy federált hitelesítési adat egy Azure alkalmazásregisztráción, és egy Munkaterhelés Identitás Pool a Google Cloud-on. Ezt egyszer csinálod; utána a pulumi-idp/auth az egyetlen dolog, amire bármelyik stack hivatkozik a hitelesítő adatokhoz. Egy stack egy blokk segítségével optál bele a stack konfigurációjában. Az AWS stack Pulumi.aws.yaml fájlja: # Pulumi.aws.yaml környezet: - pulumi-idp/auth Ez az egész hitelesítő történet. Nincs kulcs a programban, nincs kulcs a CI-ben, semmi, amit forgatni kell. Az alábbi program soha nem említi egy titkot; csak erőforrásokat hoz létre, és a pulumi-idp/auth környezeti hitelesítő adatai végzik a kérést. Egy cloud-init, három felhő Az a rész, amely egy üres Ubuntu boxot inferencia szerverré alakít, minden felhőn azonos, így egy fájlban él, cloud-init.yaml, amelyet mindhárom program olvas. Szorosan követi az Akamai cloud-configot, néhány robusztussági korrekcióval egy többfelhős futásra, és szándékos sorrendben fut, mert a GPU drivernek újra kell indítania, mielőtt az Ollama látná a kártyát: Frissítsd a csomagokat, telepítsd a kernel header-eket és ubuntu-drivers-t. Telepítsd az NVIDIA drivert, majd indíts el újra, hogy a kernel betöltse azt. A következő indításkor egy egyszeri systemd szolgáltatás telepíti az Ollama-t, bindeli 0.0.0.0:11434-re, és letölti a modellt. A szolgáltatás letiltja magát, így soha többé nem fut. #cloud-config # Zero-touch Ollama GPU box. Felhőfüggetlen: nincs provider metadata, nincs statikus creds. # A Pulumi program az ollama pull szakaszon a modellt nevére cseréli # mielőtt ezt felhasználói adatokként (AWS/GCP) vagy testreszabott adatokként (Azure) adja át. write_files: # Tedd az Ollama-t úgy, hogy minden interfészen hallgasson, és soha ne ürítse ki a modellt a VRAM-ból. - path: /etc/systemd/system/ollama.service.d/override.conf content: | [Service] Environment="OLLAMA_HOST=0.0.0.0:11434" Environment="OLLAMA_KEEP_ALIVE=-1" # Futtatja egyszer az indítás utáni, miután a GPU driver betöltődött. - path: /usr/local/bin/ollama-setup.sh permissions: '0755' content: | #!/usr/bin/env bash set -euxo pipefail # Telepíti az Ollama-t; a telepítő létrehozza és elindítja az ollama systemd egységet. curl -fsSL https://ollama.com/install.sh | sh # Vedd az OLLAMA_HOST / OLLAMA_KEEP_ALIVE felülírást, amit fent írtunk. systemctl daemon-reload systemctl enable ollama.service systemctl restart ollama.service # Várj, amíg a démon bindeli a socketjét azelőtt, hogy letöltenéd. Ez a box # a SAJÁT helyi démonját várja, nem egy külső készenléti kaput. until curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1; do sleep 2 done # Előzetesen letölti a modellt, hogy a végpont az első kérésnél válaszoljon. ollama pull __MODEL__ # Egyszeri: soha többet nem fut jövőbeli indításokkor. systemctl disable ollama-setup.service - path: /etc/systemd/system/ollama-setup.service content: | [Unit] Description=Egyszeri Ollama telepítés és modell letöltés After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/ollama-setup.sh RemainAfterExit=true # A modell letöltése több percig tart; enélkül a systemd alapértelmezett # 90s indítási időkorlát megölheti a szolgáltatást letöltés közben, és a modell sosem érkezi meg. TimeoutStartSec=0 [Install] WantedBy=multi-user.target runcmd: - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get -y upgrade # a linux-headers-generic a kernel-t is fedi, amit a fenti frissítés felhúzhat # DKMS felépíti az NVIDIA modult a következő induló kernelhez. - DEBIAN_FRONTEND=noninteractive apt-get install -y "linux-headers-$(uname -r)" linux-headers-generic ubuntu-drivers-common # --gpgpu az headless szerver driver ágat választja, ami a megfelelő választás egy számító # GPU-hoz, mint a T4; csupán az `ubuntu-drivers install` az asztali stackot húzná le. - ubuntu-drivers install --gpgpu - systemctl enable ollama-setup.service power_state: mode: reboot message: Újraindítás az NVIDIA driver betöltéséhez az Ollama beállítása előtt condition: true A modell neve a szó szerinti token __MODEL__. A Pulumi program ezt a fájlt a telepítéskor olvassa, és a te modell konfigurációs értékedet cseréli le, mielőtt átadná a példánynak. Két környezeti beállítás csendben, de fontos munkát végez: OLLAMA_HOST=0.0.0.0:11434 lehetővé teszi, hogy az Ollama minden interfészen hallgasson a localhost helyett, és az OLLAMA_KEEP_ALIVE=-1 a modellt a GPU memóriában tartja, így csak az első kérés fizeti meg a terhelési költséget. A programok Minden program azonos öt szakaszból áll: olvasd el a konfigurációt, formázd a cloud-init.yaml-t, nyisd meg a bejövő portokat, indítsd el a GPU példányt azzal a cloud-init-tel mint felhasználói adatokkal, és exportáld a végpontot. Ami eltér, pusztán az a dialektus, amelyet minden felhő használ a “GPU példány” és “tűzfal szabály” kifejezésre. A megosztott szerződés lehetővé teszi, hogy a három lista egyértelmű legyen egymás mellett: ugyanaz a modell és allowSsh konfigurációs kulcsok, ugyanaz a cloud-init.yaml, és ugyanaz a négy export (publicIp, ollamaEndpoint, generateEndpoint, tagsEndpoint). Válaszd ki a felhőd: import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; import * as fs from "fs"; const cfg = new pulumi.Config(); const model = cfg.get("model") ?? "qwen2.5:14b"; const allowSsh = cfg.getBoolean("allowSsh") ?? false; const region = (cfg.get("region") ?? "us-east-1") as aws.Region; // Injektáld a modell nevét a cloud-init-ba a telepítéskor (fájlelfogás, nem shell-kisugárzás). const userData = fs.readFileSync("cloud-init.yaml", "utf8").replace(/__MODEL__/g, model); // A régió a konfigurációból származik; a hitelesítő adatok környezeti úton érkeznek a pulumi-idp/auth-ból. const provider = new aws.Provider("aws", { region }); // Legújabb Ubuntu 24.04 LTS (amd64, hvm, gp3) a Canonical által. const ubuntu = aws.ec2.getAmi({ mostRecent: true, owners: ["099720109477"], filters: [ { name: "name", values: ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] }, { name: "virtualization-type", values: ["hvm"] }, ], }, { provider }); const ingress: aws.types.input.ec2.SecurityGroupIngress[] = [{ description: "Ollama HTTP API. MEGJEGYZÉS: korlátozd a cidrBlocks-t a saját tartományodra élesben.", fromPort: 11434, toPort: 11434, protocol: "tcp", cidrBlocks: ["0.0.0.0/0"], }]; if (allowSsh) { ingress.push({ description: "SSH", fromPort: 22, toPort: 22, protocol: "tcp", cidrBlocks: ["0.0.0.0/0"], }); } const sg = new aws.ec2.SecurityGroup("ollama", { description: "Ollama inferencing access", ingress, egress: [{ description: "Allow all outbound", fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"], }], }, { provider, }); const server = new aws.ec2.Instance("ollama", { ami: ubuntu.then(a => a.id), instanceType: "g4dn.xlarge", // 1x NVIDIA T4 vpcSecurityGroupIds: [sg.id], associatePublicIpAddress: true, userData, // sima szöveg; az AWS provider alapból base64-be kódolja neked rootBlockDevice: { volumeSize: 40, volumeType: "gp3", }, tags: { Name: "ollama" }, }, { provider, }); export const publicIp = server.publicIp; export const ollamaEndpoint = pulumi.interpolate`http://${publicIp}:11434`; export const generateEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/generate`; export const tagsEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/tags`; import * as pulumi from "@pulumi/pulumi"; import * as gcp from "@pulumi/gcp"; import * as fs from "fs"; const cfg = new pulumi.Config(); const model = cfg.get("model") ?? "qwen2.5:14b"; const allowSsh = cfg.getBoolean("allowSsh") ?? false; const project = cfg.get("project"); const zone = cfg.get("zone") ?? "us-central1-a"; // Injektáld a modell nevét a cloud-init-ba a telepítéskor (fájlelfogás, nem shell-kisugárzás). const userData = fs.readFileSync("cloud-init.yaml", "utf8").replace(/__MODEL__/g, model); // Legutolsó Ubuntu 24.04 LTS amd64 kép. const ubuntu = gcp.compute.getImage({ family: "ubuntu-2404-lts-amd64", project: "ubuntu-os-cloud", }); // A hálózati címke összekapcsolja a tűzfal szabályokat ezzel a példánnyal. const networkTag = "ollama"; const firewall = new gcp.compute.Firewall("ollama-fw", { network: "default", project: project, // Bejövő Ollama forrásból bárhonnan; korlátozd ezt a CIDR-t élesben. allows: allowSsh ? [{ protocol: "tcp", ports: ["11434"] }, { protocol: "tcp", ports: ["22"] }] : [{ protocol: "tcp", ports: ["11434"] }], sourceRanges: ["0.0.0.0/0"], targetTags: [networkTag], }); const instance = new gcp.compute.Instance("ollama", { machineType: "n1-standard-4", zone: zone, project: project, tags: [networkTag], bootDisk: { initializeParams: { image: ubuntu.then(i => i.selfLink), size: 40, }, }, guestAccelerators: [{ type: "nvidia-tesla-t4", count: 1, }], // A GPU-k nem élnek migrálva, így a gazda karbantartásának meg kell szüntetnie (és újraindítania) a VM-t. scheduling: { onHostMaintenance: "TERMINATE", automaticRestart: true, }, networkInterfaces: [{ network: "default", accessConfigs: [{}], // egy üres config kér egy ideiglenes nyilvános IP-t }], metadata: { "user-data": userData, // Az Ubuntu cloud-init ehhez a kulcshoz olvassa ezt, nem az indítási script-hez }, }, { dependsOn: firewall, }); export const publicIp = instance.networkInterfaces.apply(nics => nics[0].accessConfigs![0].natIp); export const ollamaEndpoint = pulumi.interpolate`http://${publicIp}:11434`; export const generateEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/generate`; export const tagsEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/tags`; import * as pulumi from "@pulumi/pulumi"; import * as resources from "@pulumi/azure-native/resources"; import * as network from "@pulumi/azure-native/network"; import * as compute from "@pulumi/azure-native/compute"; import * as random from "@pulumi/random"; import * as fs from "fs"; const cfg = new pulumi.Config(); const model = cfg.get("model") ?? "qwen2.5:14b"; const allowSsh = cfg.getBoolean("allowSsh") ?? false; const location = cfg.get("location") ?? "eastus"; // Injektáld a modell nevét a cloud-init-ba a telepítéskor (fájlelfogás, nem shell-kisugárzás). const userData = fs.readFileSync("cloud-init.yaml", "utf8").replace(/__MODEL__/g, model); const resourceGroup = new resources.ResourceGroup("ollama-rg", { location, }); const vnet = new network.VirtualNetwork("ollama-vnet", { resourceGroupName: resourceGroup.name, addressSpace: { addressPrefixes: ["10.0.0.0/16"] }, }); const subnet = new network.Subnet("ollama-subnet", { resourceGroupName: resourceGroup.name, virtualNetworkName: vnet.name, addressPrefix: "10.0.1.0/24", }); // Standard SKU nyilvános IP-k statikus kiosztást használnak. const publicIpAddress = new network.PublicIPAddress("ollama-pip", { resourceGroupName: resourceGroup.name, sku: { name: network.PublicIPAddressSkuName.Standard }, publicIPAllocationMethod: network.IPAllocationMethod.Static, }); // Az Azure alapértelmezés szerint engedélyez minden kimenőt, így csak a bejövő szabályokra van szükség. const nsg = new network.NetworkSecurityGroup("ollama-nsg", { resourceGroupName: resourceGroup.name, securityRules: [ { name: "allow-ollama", priority: 1000, direction: network.SecurityRuleDirection.Inbound, access: network.SecurityRuleAccess.Allow, protocol: network.SecurityRuleProtocol.Tcp, sourcePortRange: "*", destinationPortRange: "11434", sourceAddressPrefix: "0.0.0.0/0", // prod: korlátozd a saját kliens CIDR-edre destinationAddressPrefix: "*", }, ...(allowSsh ? [{ name: "allow-ssh", priority: 1001, direction: network.SecurityRuleDirection.Inbound, access: network.SecurityRuleAccess.Allow, protocol: network.SecurityRuleProtocol.Tcp, sourcePortRange: "*", destinationPortRange: "22", sourceAddressPrefix: "0.0.0.0/0", destinationAddressPrefix: "*", }] : []), ], }); const nic = new network.NetworkInterface("ollama-nic", { resourceGroupName: resourceGroup.name, networkSecurityGroup: { id: nsg.id }, ipConfigurations: [{ name: "ipconfig1", subnet: { id: subnet.id }, publicIPAddress: { id: publicIpAddress.id }, privateIPAllocationMethod: network.IPAllocationMethod.Dynamic, primary: true, }], }); // Az Azure megköveteli az admin hitelesítőt, még akkor is, ha soha nem jelentkezel be. Generálj egyet // ahelyett, hogy rögzítenéd; ez a Pulumi titok marad, és soha nem exportálódik. const adminPassword = new random.RandomPassword("ollama-admin-password", { length: 24, special: true, overrideSpecial: "!#$%*", minLower: 1, minUpper: 1, minNumeric: 1, minSpecial: 1, // Az Azure 3 a 4 karakterosztályokból megkövetel. }); const vm = new compute.VirtualMachine("ollama-vm", { resourceGroupName: resourceGroup.name, hardwareProfile: { vmSize: "Standard_NC4as_T4_v3" }, // 1x NVIDIA T4 networkProfile: { networkInterfaces: [{ id: nic.id, primary: true }], }, osProfile: { computerName: "ollama", adminUsername: "azureuser", adminPassword: adminPassword.result, customData: Buffer.from(userData).toString("base64"), // Az Azure a base64-t akarja a linuxConfiguration: { disablePasswordAuthentication: false, }, }, storageProfile: { imageReference: { publisher: "Canonical", offer: "ubuntu-24_04-lts", sku: "server", // Gen2; NC4as_T4_v3 egy Gen2 méretváltozat: "latest", }, osDisk: { name: "ollama-osdisk", createOption: "FromImage", diskSizeGB: 40, managedDisk: { storageAccountType: "StandardSSD_LRS" }, }, }, }); // Az egy standard/statikus nyilvános IP a létrehozáskor foglalt helyet, így címe ismert lesz, // miután az erőforrás létezik; külön keresés nem szükséges. export const publicIp = publicIpAddress.ipAddress.apply(ip => ip!); export const ollamaEndpoint = pulumi.interpolate`http://${publicIp}:11434`; export const generateEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/generate`; export const tagsEndpoint = pulumi.interpolate`http://${publicIp}:11434/api/tags`; Néhány felhőspecifikus részlet érdemes kiemelni, mivel ezek helyettesítik az "ugyanaz a program" absztrakciót: Az AWS a legrövidebb program, mert a GPU az instance formájával érkezik: egy g4dn.xlarge egy T4 box, így nincs külön gyorsító, amit csatlakoztatni kellene. Az egyetlen dolog, amit meg kell tenned a telepítés előtt, emelni a Kereslet szerint futó G és VT instance vCPU kvótáját a te régiódba; egy friss fiók nulláról indul, és a pulumi up sikertelen VcpuLimitExceeded-ig, amíg nem teszed. A Google Cloud explicit módon csatlakoztatja a GPU-t guestAccelerators segítségével, és ez hozza a szabályt, ami miatt sokan elbuknak: egy GPU instance nem tud élőben migrálni, így scheduling.onHostMaintenance-nek "TERMINATE"-nak kell lennie, különben a alkamazás elutasítja. A cloud-init-nek szintén a user-data metadat kulcsán kell lovagolnia, nem a startup-script-en, és az üres accessConfigs: [{}] adja meg a boxnak a nyilvános IP-t. A T4 kvóta új projektnél is nulla. Az Azure a leghosszabb lista, mert a hálózat a választásod szerint van: a resource group, a virtuális hálózat, a subnet, a nyilvános IP, a biztonsági csoport és a NIC mind a saját erőforrásuk, mielőtt elérnéd a VM-et. Egy részlet meglepi az embereket: egy Linux VM-nek admin hitelesítőt kell követelnie, még ha soha nem jelentkezel is be, így a program egy eldobható jelszót generál a random.RandomPassword segítségével ahelyett, hogy rögzítene egyet. Az NC4as_T4_v3 egy számítási GPU, így a cloud-initből a standard szerver (CUDA) driver a megfelelő választás itt; a GRID driver csak GPU-gyorsított vizualizációs feladatokhoz szükséges. A teljes programok, mindhárom Pulumi.<cloud>.yaml fájl, és az osztott cloud-init.yaml a kísérő repóban található: github.com/dirien/fully-automated-ai-inference-pulumi Telepítés Hozz létre egy projektet, telepítsd a felhőd providerét, irányítsd a stack-et a pulumi-idp/auth-ra, és telepítsd. AWS esetén: mkdir ai-inference && cd ai-inference pulumi new typescript npm install @pulumi/aws Helyezd az AWS listát az index.ts fájlba, másold a korábban bemutatott cloud-init.yaml-t ugyanabba a könyvtárba (vagy szerezd meg a kísérő repóból), irányítsd a stack-et az auth környezethez, és állítsd be a régiódat: pulumi stack init aws # add `environment: [pulumi-idp/auth]` a Pulumi.aws.yaml fájlhoz (ahogyan fentebb látható) pulumi config set region us-east-1 pulumi config set model qwen2.5:14b pulumi up A másik két felhő ugyanezt a folyamatot követi más provider csomaggal és néhány konfigurációs kulccsal: npm install @pulumi/gcp pulumi config set project <a-projekt-azonosító> pulumi config set zone us-central1-a, vagy npm install @pulumi/azure-native @pulumi/random pulumi config set location eastus. A lassú rész a GPU box: elindul, telepíti a drivert, újraindít, telepíti az Ollama-t, és letölti a modellt, mindezt a te beavatkozásod nélkül. Az instance, a régió és a modell méretétől függően körülbelül 10-15 percet tervezz, amíg a végpont válaszol. Amikor a pulumi up befejeződik, közvetlenül kapod a végpontokat stack outputként: Outputs: generateEndpoint: "http://<public-ip>:11434/api/generate" ollamaEndpoint : "http://<public-ip>:11434" publicIp : "<public-ip>" tagsEndpoint : "http://<public-ip>:11434/api/tags" Az inferencia szerver tesztelése A pulumi up parancs azonnal visszatér, amint az infrastruktúra létezik, ami a modell letöltése előtt történik. Ez pontosan az a pont, ahol a Terraform eljutott ahhoz a null_resource ciklushoz, és ahol a Pulumi megad neked egy URL-t helyette. Annak ellenőrzésére, hogy a modell készen áll-e, kérdezd meg az Ollama-t, mit töltött be: curl -s $(pulumi stack output tagsEndpoint) | grep -q "qwen2.5:14b" && echo "kész" || echo "még mindig töltődik" Amint azt jelenti, hogy kész, küldj neki egy promptot. Ez ugyanaz a kérés, amelyet az Akamai bejegyzés tesz, a generateEndpoint outputodra mutatva: curl -s $(pulumi stack output generateEndpoint) -d '{ "model": "qwen2.5:14b", "system": "Válaszolj minden kérdésre egy háromsoros verssel.", "prompt": "Miért kék az ég?", "stream": false }' Az első hívás lassabb, mert az Ollama betölti a modellt a GPU memóriába, mielőtt válaszol. Minden további hívás gyors, mivel az OLLAMA_KEEP_ALIVE=-1 a memóriában tartja: { "model": "qwen2.5:14b", "response": "A napfény szétszóródik, a rövid hullámok repülnek,\nA kék fény festi az nyitott eget,\nA lilás árnyalat eltűnik ahogy a naphalad.", "done": true } Amikor végeztél, egy parancs segítségével vedd le az egészet, hogy a GPU ne számlázzon: pulumi destroy Költségek Egy GPU instance az egész számlát jelenti, és az óránkénti óráért fizetsz, függetlenül attól, hogy a modell foglalt vagy inaktív. Az alábbi számok durva, igény szerinti árak egyetlen T4-osztályú boxra, amelyet 24/7-ben futtatnak; a gyakorlatban felpörgeted, használod, és pulumi destroy-ral lezárod, így amit ténylegesen fizetsz, a fölött álló órák számát követi. Felhő Példány GPU ~Óránként ~Havonta (24/7) AWS g4dn.xlarge 1× T4 (16 GB) ~$0.53 ~$384 Azure Standard_NC4as_T4_v3 1× T4 (16 GB) ~$0.53 ~$384 Google Cloud n1-standard-4 + 1× T4 1× T4 (16 GB) ~$0.54 ~$394 Ezek igény szerinti Linux árak egy olcsó amerikai régióban (us-east-1, eastus, us-central1) 2026 közepén, és a GPU dominál a számlában. Spot vagy preemptible kapacitás drámaian csökkenti ezt, ha a munkafolyamatod elviseli az megszakítást (gyakran 60-70%-kal olcsóbb), a Google Cloud tartós használatának kedvezménye önállóan csökkenti az mindig aktív példányt, és a 40 GB-os lemez csak néhány dollárt ad hozzá havonta. Egy GPU box, amelyet éjszakára hagynak futni, a drága hiba itt. Mivel az egész halom deklaratív, a biztonságos szokás olcsó: pulumi destroy, amikor abbahagyod a használatát, pulumi up, amikor újra szükséged van rá. Az állapot és a konfiguráció verziókezelve van, így ismét történő felállítása egy parancsot jelent, nem újjáépítést. Biztonsági szempontok A bejegyzésben szereplő architektúra szándékosan megegyezik az Akamai eredetivel, amely magában hordozza ugyanazt a figyelmeztetést: az Ollama nyílt HTTP-n keresztül érhető el, egy olyan porton, amely nyitva áll az egész internet számára. Ez rendben van egy bemutatóra egy boxon, amelyet ugyanazon a napon leépítesz. Ez nem rendben való semmi, ami túléli a délutánt, és a saját üzemeltetésű AI végpontok gyorsan megtalálhatók: az olyan szkennerek, mint a Shodan, néhány órán belül egy friss kiadást nevesítettek, és egy nyitott Ollama példány ingyenes számítás azok számára, akik először találják meg. A hitelesítő rész azonban valóban jobb, mint egy statikus token beállítás, és ez az a része, amit érdemes megtartani. Nincs hosszú életű felhő kulcs a programban, a shell-ben, vagy a CI-ben; minden telepítés rövid életű hitelesítő adatokat kap a pulumi-idp/auth segítségével, és eldobja, amikor befejeződik. Aggodalom Akamai/Terraform bemutató Ez a telepítés Felhő hitelesítő adatok Statikus PAT egy környezeti változóban OIDC bejelentkezés, rövid életű, a pulumi-idp/auth-on keresztül Bemenő expozíció 11434-es port nyitva 0.0.0.0/0 Alapértelmezés szerint ugyanez; egy konfigurációs kapcsoló határozza meg a CIDR-t Szállítás Simát HTTP Simát HTTP (TLS-el valós használathoz) Készenléti váró null_resource + local-exec shell ciklus Futási curl, nincs erőforrás Az ajánlásaim a bemutatón túli haladásra: Határozd be a bemeneti szabályt a saját CIDR-edre a 0.0.0.0/0 helyett; a programban található allowSsh kapcsoló a minta, amit másolni kell az Ollama portjáért Tedd a tűzfal elé egy fordított proxyval TLS-el, vagy tartsd a boxot távol a nyilvános internettől, és érj el egy magánhálózaton vagy egy Tailscale-hálózaton, ahogyan én a Hermes ügynökre tettem Tartsd meg a hitelesítőket OIDC-n keresztül az ESC-n; soha ne térj vissza egy statikus kulcshoz, hogy "csak működjön" Kezeld a boxot eldobhatóként, és pusztítsd el a pulumit, amikor nem használod, ami csökkenti a számlát és a támadási időablakot Mi a következő lépés? A program egy alap, és az érdekes munka az, amit akkor támadsz, amikor a modell egy pulumi up távolságra van: Zárd le. Határozd be a tűzfalat, adj hozzá TLS-t, vagy tedd a boxot egy magánhálózat mögé, így a végpont egyáltalán nem a nyilvános interneten van. Skálázd a modellt a GPU-hoz. A qwen2.5:14b egy T4-en a belépési pont. A nagyobb modelleknek több memóriára van szükségük, ami egy A10G, egy L4 vagy egy A100 szükséges, és az egyetlen változtatás a programban az instance típus és a modellérték cseréje. Cserélj le a kiszolgáló motort. Az Ollama a legegyszerűbb működő dolog. Magasabb áteresztőképesség esetén ugyanaz a struktúra érvényes a vLLM-re, amely az Ollama helyett szerepel a cloud-init-ben. Generálj egy következőt. A Pulumi Neo célja lehet például "egy GPU inferencia box Azure-on egy magán végponton" és egy első vázlatot állít elő, amit ezután át tudsz nézni egy PR-n, mint bármely más változtatást. Összefoglalás Az AI inferencia szerver kódformájú deklarálása ugyanazt hozza, mint bármely más infrastruktúra esetében: reprodukálhatod bármelyik három felhőn, verzióba helyezheted és egyetlen pulumi destroy-dal lebontod. Az Akamai projekt átültetése éles ellentétet hozott. A hitelesítők statikus tokenből OIDC bejelentkezésre váltak, amely semmit sem hagy hátra, és a készenléti várakozás teljesen eltűnt, ahogyan abbahagyod az erőforrásokként való kezelést egy futási ellenőrzésnél. Ez az a vonal, amit érdemes továbbvinni a következőhez, amit telepítesz. Nem minden lépés egy futási útmutatóban erőforrás. Egy GPU box és egy tűzfal szabály az; várakozás egy letöltés befejeződésére nem. Először húzd meg ezt a vonalat, és a program magától is rövidebbé válik. Ha problémákba ütközöl vagy kérdéseid vannak, látogass el a Pulumi közösségi Slack-be vagy a GitHub Discussions-ra. Új vagy a Pulumi-ban? Itt kezdj.

A teljes cikk az eredeti weboldalon

Külső link: pulumi.com

Kapcsolódó cikkek