In questa guida trovi un percorso completo per avviare, fermare, riavviare, descrivere e creare istanze EC2 usando Node.js e l’AWS SDK per JavaScript v3. Gli esempi sono pensati per Node 18+ e si basano su credenziali configurate nel profilo AWS o in variabili d’ambiente.
Prerequisiti
- Un account AWS con permessi su EC2 (meglio tramite un role a privilegi minimi).
- Node.js 18 o superiore installato.
- Credenziali configurate con
aws configure
oppure variabili d’ambienteAWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
.
Esempio di policy IAM a privilegi minimi per gestione base
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances"
], "Resource": "*" }
]
}
Se devi anche creare/terminare istanze, aggiungi: ec2:RunInstances
, ec2:TerminateInstances
, ec2:CreateTags
, e i permessi correlati a key pair e security group.
Installazione dei pacchetti
npm init -y
npm i @aws-sdk/client-ec2 @aws-sdk/credential-providers
Configurazione del client EC2
// ec2-client.js
import { EC2Client } from "@aws-sdk/client-ec2";
import { fromEnv } from "@aws-sdk/credential-providers";
export const ec2 = new EC2Client({
region: process.env.AWS_REGION || "eu-central-1",
credentials: process.env.AWS_ACCESS_KEY_ID
? fromEnv()
: undefined // userà profilo/role locale se disponibile
});
Operazioni comuni su un’istanza esistente
Supponiamo di avere l’ID dell’istanza, ad esempio i-0123456789abcdef0
. Crea funzioni riutilizzabili:
Descrivere stato e dettagli
// describe.js
import { DescribeInstancesCommand } from "@aws-sdk/client-ec2";
import { ec2 } from "./ec2-client.js";
export async function describeInstance(instanceId) {
const cmd = new DescribeInstancesCommand({ InstanceIds: [instanceId] });
const res = await ec2.send(cmd);
const reservation = res.Reservations?.[0];
const inst = reservation?.Instances?.[0];
if (!inst) throw new Error("Istanza non trovata");
return {
id: inst.InstanceId,
state: inst.State?.Name,
type: inst.InstanceType,
az: inst.Placement?.AvailabilityZone,
publicIp: inst.PublicIpAddress,
privateIp: inst.PrivateIpAddress,
launchTime: inst.LaunchTime
};
}
Avviare, fermare, riavviare
// lifecycle.js
import {
StartInstancesCommand,
StopInstancesCommand,
RebootInstancesCommand
} from "@aws-sdk/client-ec2";
import { ec2 } from "./ec2-client.js";
export async function startInstance(instanceId) {
await ec2.send(new StartInstancesCommand({ InstanceIds: [instanceId] }));
}
export async function stopInstance(instanceId, hibernate = false) {
await ec2.send(new StopInstancesCommand({
InstanceIds: [instanceId],
Hibernate: hibernate
}));
}
export async function rebootInstance(instanceId) {
await ec2.send(new RebootInstancesCommand({ InstanceIds: [instanceId] }));
}
Attendere transizioni di stato (waiters)
I waiters possono attendere in modo affidabile che l’istanza sia running o stopped prima di procedere.
// waiters.js
import {
waitUntilInstanceRunning,
waitUntilInstanceStopped
} from "@aws-sdk/client-ec2";
import { ec2 } from "./ec2-client.js";
export async function waitRunning(instanceId, maxWaitSeconds = 300) {
const out = await waitUntilInstanceRunning(
{ client: ec2, maxWaitTime: maxWaitSeconds },
{ InstanceIds: [instanceId] }
);
if (out.state !== "SUCCESS") throw new Error("Timeout in attesa di running");
}
export async function waitStopped(instanceId, maxWaitSeconds = 300) {
const out = await waitUntilInstanceStopped(
{ client: ec2, maxWaitTime: maxWaitSeconds },
{ InstanceIds: [instanceId] }
);
if (out.state !== "SUCCESS") throw new Error("Timeout in attesa di stopped");
}
Creare una nuova istanza
Per creare un’istanza occorrono: AMI, tipo istanza, key pair (per SSH), security group e subnet. Qui un esempio minimo con tag e user data per installare Nginx su Amazon Linux 2023.
// create-instance.js
import {
RunInstancesCommand,
CreateTagsCommand,
DescribeImagesCommand
} from "@aws-sdk/client-ec2";
import { ec2 } from "./ec2-client.js";
// Facoltativo: recupera l'AMI più recente di Amazon Linux 2023 (x86_64) per la regione
async function getLatestAL2023Ami() {
const res = await ec2.send(new DescribeImagesCommand({
Owners: ["amazon"],
Filters: [
{ Name: "name", Values: ["al2023-ami-*-x86_64"] },
{ Name: "state", Values: ["available"] },
{ Name: "architecture", Values: ["x86_64"] },
{ Name: "root-device-type", Values: ["ebs"] }
]
}));
const images = res.Images ?? [];
images.sort((a, b) => new Date(b.CreationDate) - new Date(a.CreationDate));
if (!images[0]?.ImageId) throw new Error("AMI non trovata");
return images[0].ImageId;
}
export async function createInstance({
amiId,
instanceType = "t3.micro",
keyName,
securityGroupIds,
subnetId,
tags = { Project: "demo-node-ec2" }
}) {
const imageId = amiId || await getLatestAL2023Ami();
const userData = Buffer.from(`#!/bin/bash
dnf -y update
dnf -y install nginx
systemctl enable nginx
systemctl start nginx
`).toString("base64");
const run = new RunInstancesCommand({
ImageId: imageId,
InstanceType: instanceType,
KeyName: keyName,
SecurityGroupIds: securityGroupIds,
SubnetId: subnetId,
MinCount: 1,
MaxCount: 1,
TagSpecifications: [
{
ResourceType: "instance",
Tags: Object.entries(tags).map(([Key, Value]) => ({ Key, Value }))
}
],
UserData: userData
});
const out = await ec2.send(run);
const instanceId = out.Instances?.[0]?.InstanceId;
if (!instanceId) throw new Error("Creazione istanza fallita");
// (Opzionale) aggiungi tag al volume root
try {
const volumeId = out.Instances?.[0]?.BlockDeviceMappings?.[0]?.Ebs?.VolumeId;
if (volumeId) {
await ec2.send(new CreateTagsCommand({
Resources: [volumeId],
Tags: Object.entries(tags).map(([Key, Value]) => ({ Key, Value }))
}));
}
} catch {}
return instanceId;
}
Gestione sicura di credenziali e ruoli
- Preferisci l’uso di IAM Role (ad es. su un runner in EC2/CodeBuild) invece di chiavi statiche.
- Per ambienti locali, usa profili nel file
~/.aws/credentials
eAWS_PROFILE
. - Limita i permessi al minimo necessario: separa la policy “read/describe” da quella “lifecycle”.
Best practice operative
- Idempotenza: proteggi i comandi ripetuti (es. controlla lo stato prima di chiamare
StartInstances
/StopInstances
). - Retry e backoff: le API AWS possono rispondere con throttling; usa retry esponenziale se integri orchestrazioni più complesse.
- Waiters: affidati ai waiters per stati consistenti prima di eseguire step successivi (allocation IP, tagging, health check).
- Tagging: applica tag coerenti per cost allocation e ricerca (
Project
,Env
,Owner
). - Sicurezza: key pair private va protetta; restringi i security group (SSH solo dal tuo IP, HTTP/HTTPS secondo necessità).
- Spegnimento: automatizza lo stop di istanze non usate per contenere i costi (es. cron esterno o Lambda Scheduler).
Diagnostica rapida
// utils-status.js
import { DescribeInstanceStatusCommand } from "@aws-sdk/client-ec2";
import { ec2 } from "./ec2-client.js";
export async function instanceChecks(instanceId) {
const res = await ec2.send(new DescribeInstanceStatusCommand({
InstanceIds: [instanceId],
IncludeAllInstances: true
}));
return res.InstanceStatuses?.[0] ?? null;
}
Conclusione
Con l’AWS SDK v3 e poche funzioni ben strutturate puoi orchestrare l’intero ciclo di vita di un’istanza EC2 direttamente da Node.js: dalla creazione con user data al controllo dello stato, fino allo stop programmato. Adatta gli esempi alle tue regole IAM, ai tuoi security group e alle tue esigenze di rete e osservabilità.