Questa guida mostra come avviare, fermare, riavviare, descrivere e creare istanze EC2 usando Go con l’AWS SDK v2. Gli esempi richiedono Go 1.21+, moduli Go attivati e credenziali configurate via profilo AWS o variabili d’ambiente.
Prerequisiti
- Account AWS con permessi su EC2 (principio del least privilege).
- Go 1.21 o superiore.
- Credenziali configurate con
aws configure
oppure variabiliAWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
.
Esempio di policy IAM minima
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances"
],
"Resource": "*"
}
]
}
Per creare/terminare istanze aggiungi: ec2:RunInstances
, ec2:TerminateInstances
, ec2:CreateTags
, oltre ai permessi su key pair e security group.
Installazione moduli
go mod init example.com/ec2-go
go get github.com/aws/aws-sdk-go-v2@latest
go get github.com/aws/aws-sdk-go-v2/config@latest
go get github.com/aws/aws-sdk-go-v2/service/ec2@latest
Configurazione del client EC2
// internal/aws/ec2client/ec2client.go
package ec2client
import (
"context"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func New(ctx context.Context) (*ec2.Client, error) {
region := os.Getenv("AWS_REGION")
opts := []func(*config.LoadOptions) error{}
if region != "" {
opts = append(opts, config.WithRegion(region))
}
cfg, err := config.LoadDefaultConfig(ctx, opts...)
if err != nil {
return nil, err
}
return ec2.NewFromConfig(cfg), nil
}
Operazioni comuni su istanza esistente
Supponiamo di avere l’ID istanza, ad es. i-0123456789abcdef0
. Creiamo funzioni riutilizzabili:
Describe: stato e dettagli
// internal/ec2ops/describe.go
package ec2ops
import (
"context"
"errors"
"time"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
type InstanceInfo struct {
ID string
State string
Type string
AZ string
PublicIP string
PrivateIP string
Launch string
}
func DescribeInstance(ctx context.Context, cli *ec2.Client, instanceID string) (*InstanceInfo, error) {
out, err := cli.DescribeInstances(ctx, &ec2.DescribeInstancesInput{
InstanceIds: []string{instanceID},
})
if err != nil {
return nil, err
}
if len(out.Reservations) == 0 || len(out.Reservations[0].Instances) == 0 {
return nil, errors.New("istanza non trovata")
}
inst := out.Reservations[0].Instances[0]
info := &InstanceInfo{
ID: value(inst.InstanceId),
State: value(inst.State.Name),
Type: string(inst.InstanceType),
AZ: value(inst.Placement.AvailabilityZone),
PublicIP: value(inst.PublicIpAddress),
PrivateIP: value(inst.PrivateIpAddress),
Launch: inst.LaunchTime.In(time.Local).Format("2006-01-02 15:04:05"),
}
return info, nil
}
func value[T ~string](p *T) string {
if p == nil {
return ""
}
return string(*p)
}
Avviare, fermare, riavviare
// internal/ec2ops/lifecycle.go
package ec2ops
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func Start(ctx context.Context, cli *ec2.Client, id string) error {
_, err := cli.StartInstances(ctx, &ec2.StartInstancesInput{InstanceIds: []string{id}})
return err
}
func Stop(ctx context.Context, cli *ec2.Client, id string, hibernate bool) error {
_, err := cli.StopInstances(ctx, &ec2.StopInstancesInput{
InstanceIds: []string{id},
Hibernate: &hibernate,
})
return err
}
func Reboot(ctx context.Context, cli *ec2.Client, id string) error {
_, err := cli.RebootInstances(ctx, &ec2.RebootInstancesInput{InstanceIds: []string{id}})
return err
}
Attendere transizioni di stato (waiters)
Usiamo i waiter nativi per attendere running o stopped con timeout.
// internal/ec2ops/waiters.go
package ec2ops
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func WaitRunning(ctx context.Context, cli *ec2.Client, id string, maxWait time.Duration) error {
waiter := ec2.NewInstanceRunningWaiter(cli)
ctx, cancel := context.WithTimeout(ctx, maxWait)
defer cancel()
return waiter.Wait(ctx, &ec2.DescribeInstancesInput{InstanceIds: []string{id}}, 10*time.Second)
}
func WaitStopped(ctx context.Context, cli *ec2.Client, id string, maxWait time.Duration) error {
waiter := ec2.NewInstanceStoppedWaiter(cli)
ctx, cancel := context.WithTimeout(ctx, maxWait)
defer cancel()
return waiter.Wait(ctx, &ec2.DescribeInstancesInput{InstanceIds: []string{id}}, 10*time.Second)
}
Creare una nuova istanza
Per creare un’istanza servono AMI, tipo, key pair (SSH), security group e subnet. Esempio con tag e user data per installare Nginx su Amazon Linux 2023.
// internal/ec2ops/create.go
package ec2ops
import (
"context"
"encoding/base64"
"errors"
"sort"
"time"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
func LatestAL2023AMI(ctx context.Context, cli *ec2.Client) (string, error) {
out, err := cli.DescribeImages(ctx, &ec2.DescribeImagesInput{
Owners: []string{"amazon"},
Filters: []ec2types.Filter{
{Name: str("name"), Values: []string{"al2023-ami-*-x86_64"}},
{Name: str("state"), Values: []string{"available"}},
{Name: str("architecture"), Values: []string{"x86_64"}},
{Name: str("root-device-type"), Values: []string{"ebs"}},
},
})
if err != nil {
return "", err
}
images := out.Images
sort.Slice(images, func(i, j int) bool {
ti, _ := time.Parse(time.RFC3339, value(images[i].CreationDate))
tj, _ := time.Parse(time.RFC3339, value(images[j].CreationDate))
return ti.After(tj)
})
if len(images) == 0 || images[0].ImageId == nil {
return "", errors.New("AMI non trovata")
}
return *images[0].ImageId, nil
}
type CreateParams struct {
AMI string
InstanceType string
KeyName string
SecurityGroupIDs []string
SubnetID string
Tags map[string]string
}
func CreateInstance(ctx context.Context, cli *ec2.Client, params CreateParams) (string, error) {
imageID := params.AMI
var err error
if imageID == "" {
imageID, err = LatestAL2023AMI(ctx, cli)
if err != nil {
return "", err
}
}
userData := base64.StdEncoding.EncodeToString([]byte(`#!/bin/bash
dnf -y update
dnf -y install nginx
systemctl enable nginx
systemctl start nginx
`))
out, err := cli.RunInstances(ctx, &ec2.RunInstancesInput{
ImageId: &imageID,
InstanceType: ec2types.InstanceType(params.InstanceType),
KeyName: sp(params.KeyName),
SecurityGroupIds: params.SecurityGroupIDs,
SubnetId: sp(params.SubnetID),
MinCount: ip(1),
MaxCount: ip(1),
UserData: &userData,
TagSpecifications: []ec2types.TagSpecification{
{
ResourceType: ec2types.ResourceTypeInstance,
Tags: toTags(params.Tags),
},
},
})
if err != nil {
return "", err
}
if len(out.Instances) == 0 || out.Instances[0].InstanceId == nil {
return "", errors.New("creazione istanza fallita")
}
return *out.Instances[0].InstanceId, nil
}
func toTags(m map[string]string) []ec2types.Tag {
var tags []ec2types.Tag
for k, v := range m {
k, v := k, v
tags = append(tags, ec2types.Tag{Key: &k, Value: &v})
}
return tags
}
func sp(s string) *string { if s == "" { return nil }; return &s }
func ip(v int32) *int32 { return &v }
func str(s string) *string { return &s }
Gestione sicura di credenziali e ruoli
- Preferisci IAM Role su runner/istanze rispetto a chiavi statiche.
- Per sviluppo locale, usa profili in
~/.aws/credentials
eAWS_PROFILE
. - Separa policy di lettura (describe) da quelle di ciclo di vita (start/stop/run/reboot).
Best practice operative
- Idempotenza: controlla lo stato prima di invocare
StartInstances
/StopInstances
. - Backoff: gestisci il throttling con retry esponenziale (eventuale middleware).
- Waiters: usa i waiter per stati consistenti prima di step successivi (assegnazione IP, health check, tagging).
- Tagging: adotta tag coerenti per cost allocation (
Project
,Env
,Owner
). - Sicurezza: proteggi la private key del key pair; restringi i Security Group (SSH solo dal tuo IP).
- Spegnimento: programma stop delle istanze non usate (cron/CI).
Diagnostica rapida
// internal/ec2ops/status.go
package ec2ops
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func StatusChecks(ctx context.Context, cli *ec2.Client, id string) (*ec2.DescribeInstanceStatusOutput, error) {
return cli.DescribeInstanceStatus(ctx, &ec2.DescribeInstanceStatusInput{
InstanceIds: []string{id},
IncludeAllInstances: boolp(true),
})
}
func boolp(b bool) *bool { return &b }
Variabili d’ambiente utili
AWS_ACCESS_KEY_ID=<la_tua_access_key>
AWS_SECRET_ACCESS_KEY=<la_tua_secret_key>
AWS_REGION=eu-central-1
AWS_PROFILE=default
Conclusione
Con l’AWS SDK v2 per Go e poche funzioni ben strutturate puoi orchestrare l’intero ciclo di vita di un’istanza EC2: dalla creazione con user data al controllo dello stato, fino allo stop programmato. Adatta questi esempi alle tue policy IAM, ai tuoi security group e ai requisiti di rete e osservabilità.