DevToolBox무료
블로그

AWS 가이드: 개발자를 위한 필수 클라우드 서비스 (EC2, S3, Lambda, RDS)

15분 읽기by DevToolBox
TL;DR

AWS is the leading cloud platform with 200+ services. For most applications, master these six: EC2 (virtual machines), S3 (object storage), Lambda (serverless compute), RDS (managed databases), CloudFront (CDN), and IAM (identity/access control). Use the AWS Free Tier for learning, set billing alerts immediately, and use IAM roles instead of long-lived access keys.

Amazon Web Services (AWS) is the world's most comprehensive cloud platform with over 200 fully featured services spanning compute, storage, networking, databases, AI/ML, security, and more. Launched in 2006 with just S3 and EC2, AWS now powers millions of applications from startups to Fortune 500 enterprises. Whether you are running a personal project or architecting a globally distributed system, understanding the essential AWS services is a critical skill for modern developers. This guide covers the most important services you will use day-to-day, with real CLI commands, IAM policy examples, architecture patterns, and cost optimization strategies.

Key Takeaways
  • IAM roles are always safer than IAM users with access keys — use them for EC2 instances, Lambda functions, and CI/CD pipelines.
  • S3 + CloudFront is the cheapest and most scalable way to host static websites and serve large files globally.
  • Lambda cold starts affect Node.js/Python less than Java/C# — keep functions warm with provisioned concurrency for latency-sensitive workloads.
  • RDS Multi-AZ provides automatic failover; Read Replicas provide read scalability — they are separate features, use both if needed.
  • Always tag every AWS resource with at minimum: Project, Environment, Owner — it makes cost allocation and cleanup vastly easier.
  • Use AWS Cost Explorer and set billing alarms before you start — unexpected bills are the most common AWS beginner mistake.

AWS Core Concepts: Regions, Availability Zones, and IAM

Before diving into specific services, you need to understand how AWS organizes its global infrastructure and controls access.

Regions and Availability Zones

AWS operates in 33+ geographic Regions, each containing 3+ Availability Zones (AZs). A Region is an isolated geographic area (e.g., us-east-1, eu-west-1, ap-southeast-1). An AZ is a physically separate data center within a region with independent power, cooling, and networking. Deploying across multiple AZs protects against data center failures. Some services (IAM, CloudFront, Route 53) are global; most (EC2, RDS, Lambda) are regional.

# List all regions
aws ec2 describe-regions --output table

# Set a default region in environment
export AWS_DEFAULT_REGION=us-east-1

# Set region in AWS CLI profile
aws configure set region us-east-1

# List AZs in current region
aws ec2 describe-availability-zones \
  --query "AvailabilityZones[].ZoneName" \
  --output text

IAM: Identity and Access Management

IAM is the backbone of AWS security. It controls who can access AWS services and what actions they can perform. The core components are: Users (people or apps with long-term credentials), Groups (collections of users), Roles (assumed by AWS services or external identities — no static credentials), and Policies (JSON documents defining permissions). The golden rule: always use roles over access keys, and apply the principle of least privilege.

# Example IAM policy — least privilege for S3 read on specific bucket
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadSpecificBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-app-bucket",
        "arn:aws:s3:::my-app-bucket/*"
      ]
    }
  ]
}

# Create an IAM role for EC2 (trust policy)
aws iam create-role \
  --role-name MyEC2Role \
  --assume-role-policy-document file://ec2-trust-policy.json

# Attach a managed policy
aws iam attach-role-policy \
  --role-name MyEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Create instance profile and add role
aws iam create-instance-profile --instance-profile-name MyEC2Profile
aws iam add-role-to-instance-profile \
  --instance-profile-name MyEC2Profile \
  --role-name MyEC2Role

Understanding AWS Pricing

AWS uses a pay-as-you-go model. Key pricing concepts: On-Demand (pay per second/hour, no commitment), Reserved Instances (1-3 year commitment, up to 72% savings), Spot Instances (unused capacity, up to 90% savings but can be interrupted), Savings Plans (flexible commitment across instance families), and Free Tier (12 months free for new accounts on select services). Always use the AWS Pricing Calculator before deploying production workloads.

EC2: Elastic Compute Cloud

EC2 provides resizable virtual machines (called instances) in the cloud. It is the foundation of many AWS architectures. You choose the operating system, instance type, storage, and networking configuration.

EC2 Instance Type Families

AWS offers hundreds of instance types organized into families. The name format is: [family][generation].[size] (e.g., t3.micro, m5.xlarge, c6i.2xlarge).

FamilyPurposeExamplesUse Cases
t3/t4gBurstable General Purposet3.micro, t3.small, t4g.mediumDev/test, low-traffic web apps, small DBs
m5/m6iGeneral Purposem5.large, m6i.xlarge, m6a.2xlargeWeb apps, app servers, small-medium DBs
c5/c6iCompute Optimizedc5.large, c6i.2xlarge, c6g.4xlargeBatch processing, ML inference, gaming
r5/r6iMemory Optimizedr5.large, r6i.2xlarge, x2idn.32xlargeIn-memory DBs, Redis, Elasticsearch, SAP
i3/i4iStorage Optimizedi3.large, i4i.xlarge, d3en.2xlargeNoSQL DBs, data warehousing, Kafka
p3/p4dGPU Instancesp3.2xlarge, p4d.24xlarge, g5.xlargeML training, GPU rendering, HPC

Security Groups

Security groups act as virtual firewalls for your EC2 instances. They control inbound and outbound traffic at the instance level. Unlike NACLs, security groups are stateful: if you allow inbound traffic, the response is automatically allowed.

# Create a security group
aws ec2 create-security-group \
  --group-name my-web-sg \
  --description "Web server security group" \
  --vpc-id vpc-0123456789abcdef0

# Allow HTTP from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

# Allow SSH from specific IP only
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 22 --cidr 203.0.113.0/32

# Allow traffic from another security group (e.g., ALB → EC2)
aws ec2 authorize-security-group-ingress \
  --group-id sg-ec2 \
  --protocol tcp --port 8080 \
  --source-group sg-alb

User Data Scripts

User data scripts run on instance launch and are perfect for bootstrapping: installing software, pulling configs, or registering the instance with a service.

#!/bin/bash
# EC2 User Data — runs as root on first boot
set -euo pipefail

# Update packages
yum update -y

# Install Node.js 20 via NVM
export HOME=/root
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.nvm/nvm.sh
nvm install 20
nvm use 20

# Install PM2 globally
npm install -g pm2

# Clone application from S3
aws s3 cp s3://my-app-bucket/app.tar.gz /tmp/app.tar.gz
mkdir -p /opt/app
tar -xzf /tmp/app.tar.gz -C /opt/app
cd /opt/app && npm install --production

# Start the application
pm2 start server.js --name my-app
pm2 startup
pm2 save

# Signal CloudFormation that instance is ready
# (if using cfn-init)
# /opt/aws/bin/cfn-signal -e $? ...
# Launch an EC2 instance with user data
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.small \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --subnet-id subnet-0123456789abcdef0 \
  --iam-instance-profile Name=MyEC2Profile \
  --user-data file://user-data.sh \
  --tag-specifications \
    "ResourceType=instance,Tags=[{Key=Name,Value=my-server},{Key=Environment,Value=production}]" \
  --count 1

S3: Simple Storage Service

S3 is AWS's object storage service. It stores files (objects) in containers (buckets). S3 offers 11 nines (99.999999999%) of durability, lifecycle management, versioning, server-side encryption, and static website hosting.

S3 Storage Classes

S3 offers several storage classes optimized for different access patterns and cost requirements.

Storage ClassRetrievalMin DurationCostUse Case
S3 StandardInstantNone$$Frequently accessed data
S3 Intelligent-TieringInstantNone$-$$Unknown/changing patterns
S3 Standard-IAInstant30 days$Infrequent access, rapid when needed
S3 One Zone-IAInstant30 days$Non-critical, reproductible data
S3 Glacier InstantInstant90 days¢Archive with instant retrieval
S3 Glacier FlexibleMinutes-hours90 days¢Long-term archive, rare access
S3 Glacier Deep Archive12 hours180 days¢¢Regulatory/compliance archive
# Create a bucket
aws s3api create-bucket \
  --bucket my-app-bucket-2026 \
  --region us-east-1

# For regions other than us-east-1, add LocationConstraint
aws s3api create-bucket \
  --bucket my-app-bucket-2026 \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1

# Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-app-bucket-2026 \
  --versioning-configuration Status=Enabled

# Enable server-side encryption (SSE-S3)
aws s3api put-bucket-encryption \
  --bucket my-app-bucket-2026 \
  --server-side-encryption-configuration \
    "{\"Rules\":[{\"ApplyServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]}"

# Block all public access (recommended default)
aws s3api put-public-access-block \
  --bucket my-app-bucket-2026 \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Upload a file
aws s3 cp ./index.html s3://my-app-bucket-2026/

# Sync a local directory to S3 (like rsync)
aws s3 sync ./dist s3://my-app-bucket-2026/ --delete

# Set cache headers for static assets
aws s3 sync ./dist s3://my-app-bucket-2026/ \
  --cache-control "max-age=31536000,public" \
  --exclude "*.html" \
  --delete

# Upload HTML files with no-cache
aws s3 sync ./dist s3://my-app-bucket-2026/ \
  --include "*.html" \
  --cache-control "no-cache,no-store,must-revalidate"

Static Website Hosting

S3 can host static websites directly without any servers. Combined with CloudFront, this is the cheapest way to serve static content globally.

# Enable static website hosting
aws s3api put-bucket-website \
  --bucket my-app-bucket-2026 \
  --website-configuration \
    "{\"IndexDocument\":{\"Suffix\":\"index.html\"},\"ErrorDocument\":{\"Key\":\"404.html\"}}""

# Set bucket policy for public read (required for static hosting)
cat > bucket-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "PublicReadGetObject",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-app-bucket-2026/*"
  }]
}
EOF
aws s3api put-bucket-policy \
  --bucket my-app-bucket-2026 \
  --policy file://bucket-policy.json

# Your website is now at:
# http://my-app-bucket-2026.s3-website-us-east-1.amazonaws.com
# Point CloudFront at this origin for HTTPS + CDN

Presigned URLs

Presigned URLs allow temporary, authenticated access to private S3 objects without exposing your credentials. They are essential for user file uploads and secure downloads.

# CLI: Generate presigned GET URL (valid 1 hour)
aws s3 presign s3://my-app-bucket-2026/private-file.pdf \
  --expires-in 3600

# Node.js SDK v3: Presigned PUT (for user uploads)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({ region: "us-east-1" });

async function getUploadUrl(key: string, contentType: string) {
  const command = new PutObjectCommand({
    Bucket: "my-app-bucket-2026",
    Key: key,
    ContentType: contentType,
  });

  // URL expires in 15 minutes
  const url = await getSignedUrl(s3, command, { expiresIn: 900 });
  return url;
}

// Client uploads directly to S3 (no server bandwidth used)
const url = await getUploadUrl("uploads/avatar.jpg", "image/jpeg");
await fetch(url, {
  method: "PUT",
  body: file,
  headers: { "Content-Type": "image/jpeg" },
});

Lambda: Serverless Functions

Lambda runs your code without provisioning or managing servers. You pay only for the compute time you consume. Lambda supports Node.js, Python, Go, Java, Ruby, .NET, and custom runtimes.

Lambda Triggers

Lambda functions can be triggered by dozens of AWS services and events.

Trigger SourceInvocation TypeCommon Use Case
API Gateway / Function URLSynchronousREST APIs, webhooks
S3 (ObjectCreated, ObjectRemoved)AsynchronousImage processing, file transformations
DynamoDB StreamsStreamChange data capture, event sourcing
SQSPoll-basedMessage processing, job queues
SNSAsynchronousFan-out notifications
EventBridge (CloudWatch Events)AsynchronousScheduled tasks, event routing
Kinesis Data StreamsStreamReal-time analytics, log processing
CognitoSynchronousCustom auth flows, user pool triggers
CloudFront (Lambda@Edge)SynchronousRequest/response manipulation at edge
// Lambda handler best practices — Node.js 20
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";

// Initialize clients OUTSIDE the handler (reused across warm invocations)
const dynamo = new DynamoDBClient({ region: process.env.AWS_REGION });

// Type the event — import from @types/aws-lambda
export const handler = async (event: AWSLambda.APIGatewayProxyEventV2) => {
  // Always log structured JSON for CloudWatch Insights queries
  console.log(JSON.stringify({ event: "request", path: event.rawPath }));

  try {
    const body = JSON.parse(event.body || "{}") as { name: string };

    await dynamo.send(new PutItemCommand({
      TableName: process.env.TABLE_NAME!,
      Item: marshall({
        pk: "USER#" + Date.now(),
        name: body.name,
        createdAt: new Date().toISOString(),
      }),
    }));

    return {
      statusCode: 201,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ success: true }),
    };
  } catch (err) {
    console.error(JSON.stringify({ error: String(err) }));
    return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) };
  }
};

# Deploy Lambda via AWS CLI
# 1. Create deployment package
npm run build && zip -r function.zip dist/ node_modules/

# 2. Create the function
aws lambda create-function \
  --function-name my-api-handler \
  --runtime nodejs20.x \
  --role arn:aws:iam::123456789012:role/LambdaExecutionRole \
  --handler dist/index.handler \
  --zip-file fileb://function.zip \
  --timeout 30 \
  --memory-size 256 \
  --environment "Variables={TABLE_NAME=my-table,LOG_LEVEL=info}"

# 3. Update function code (redeploy)
aws lambda update-function-code \
  --function-name my-api-handler \
  --zip-file fileb://function.zip

# 4. Enable Provisioned Concurrency (eliminate cold starts)
aws lambda put-provisioned-concurrency-config \
  --function-name my-api-handler \
  --qualifier prod \
  --provisioned-concurrent-executions 5

Understanding Cold Starts

A cold start occurs when Lambda has to initialize a new execution environment for your function. This adds latency: 100ms-1s for Node.js/Python, 1-5s for Java/C#. Strategies to minimize cold starts include keeping functions small, using Provisioned Concurrency for latency-sensitive functions, initializing SDK clients outside the handler, and using lighter runtimes (Node.js over Java).

Lambda Layers

Layers let you share code and dependencies across multiple Lambda functions. A layer is a ZIP archive containing libraries, custom runtimes, or data. Functions can use up to 5 layers, and layers can be versioned.

# Create a Lambda layer (e.g., shared utilities + node_modules)
# Layer structure: nodejs/node_modules/...
mkdir -p layer/nodejs
cp -r node_modules layer/nodejs/
cd layer && zip -r ../shared-deps-layer.zip nodejs/

# Publish the layer
aws lambda publish-layer-version \
  --layer-name shared-dependencies \
  --description "Shared node_modules for production" \
  --zip-file fileb://shared-deps-layer.zip \
  --compatible-runtimes nodejs18.x nodejs20.x

# Attach layer to a function
aws lambda update-function-configuration \
  --function-name my-api-handler \
  --layers arn:aws:lambda:us-east-1:123456789012:layer:shared-dependencies:3

API Gateway: REST and HTTP APIs

API Gateway is the front door for your Lambda functions and backend services. It handles authentication, throttling, caching, CORS, and request/response transformations.

REST API vs HTTP API

API Gateway offers two main API types. REST APIs are more feature-rich (request validation, usage plans, caching, WAF integration) but cost more. HTTP APIs are simpler, faster (lower latency), and cost 70% less. Use HTTP APIs for Lambda integrations and REST APIs when you need advanced features.

FeatureREST APIHTTP API
Pricing (per million)$3.50$1.00
LatencyHigherLower (~60% faster)
Request ValidationYesNo
WAF IntegrationYesNo
Usage Plans / API KeysYesNo
Response CachingYesNo
Lambda Proxy IntegrationYesYes (optimized)
JWT AuthorizersVia LambdaNative
CORSManualAuto-configure
Private API (VPC)YesNo
# Create an HTTP API (recommended for Lambda)
aws apigatewayv2 create-api \
  --name my-http-api \
  --protocol-type HTTP \
  --cors-configuration \
    "AllowOrigins=[https://myapp.com],AllowMethods=[GET,POST,PUT,DELETE],AllowHeaders=[Content-Type,Authorization]"

# Create Lambda integration
aws apigatewayv2 create-integration \
  --api-id abc123 \
  --integration-type AWS_PROXY \
  --integration-uri arn:aws:lambda:us-east-1:123456789012:function:my-api-handler \
  --payload-format-version 2.0

# Create a route
aws apigatewayv2 create-route \
  --api-id abc123 \
  --route-key "POST /users" \
  --target integrations/xyz789

# Add a JWT authorizer
aws apigatewayv2 create-authorizer \
  --api-id abc123 \
  --name CognitoJWT \
  --authorizer-type JWT \
  --identity-source "$request.header.Authorization" \
  --jwt-configuration \
    "Issuer=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123,Audience=[my-app-client-id]"

RDS: Relational Database Service

RDS provides managed relational databases: MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, and Aurora. AWS handles backups, patching, Multi-AZ failover, and read replicas.

Multi-AZ Deployments

Multi-AZ creates a synchronous standby replica in a different AZ. In case of primary failure, RDS automatically fails over to the standby (typically 1-2 minutes). Multi-AZ is for high availability, NOT for read scaling. The standby is not readable — use Read Replicas for that.

# Create an RDS PostgreSQL instance with Multi-AZ
aws rds create-db-instance \
  --db-instance-identifier my-postgres-prod \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --engine-version 15.4 \
  --master-username admin \
  --master-user-password "$(openssl rand -base64 32)" \
  --allocated-storage 100 \
  --storage-type gp3 \
  --multi-az \
  --backup-retention-period 7 \
  --deletion-protection \
  --enable-performance-insights \
  --db-subnet-group-name my-db-subnet-group \
  --vpc-security-group-ids sg-0db123456789

# Create a Read Replica in same region
aws rds create-db-instance-read-replica \
  --db-instance-identifier my-postgres-replica \
  --source-db-instance-identifier my-postgres-prod \
  --db-instance-class db.t3.medium

# Promote replica to standalone (e.g., for region failover)
aws rds promote-read-replica \
  --db-instance-identifier my-postgres-replica

# Enable IAM database authentication
aws rds modify-db-instance \
  --db-instance-identifier my-postgres-prod \
  --enable-iam-database-authentication \
  --apply-immediately

Snapshots and Backups

RDS supports automated backups (1-35 day retention) and manual snapshots that persist until deleted. You can restore to any point-in-time within the retention period or create a new instance from a snapshot.

# Create a manual snapshot
aws rds create-db-snapshot \
  --db-instance-identifier my-postgres-prod \
  --db-snapshot-identifier my-postgres-backup-2026-02-27

# Restore from snapshot (creates a new instance)
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier my-postgres-restored \
  --db-snapshot-identifier my-postgres-backup-2026-02-27 \
  --db-instance-class db.t3.medium

# Restore to a specific point in time
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier my-postgres-prod \
  --target-db-instance-identifier my-postgres-pitr \
  --restore-time 2026-02-26T12:00:00Z

DynamoDB: NoSQL at Scale

DynamoDB is a fully managed NoSQL database with single-digit millisecond latency at any scale. It is schemaless, serverless, and automatically handles partitioning. It is ideal for high-traffic applications where read/write patterns are predictable.

Indexes: GSI and LSI

DynamoDB has two types of secondary indexes. Local Secondary Indexes (LSI) share the partition key but have a different sort key — they must be created at table creation. Global Secondary Indexes (GSI) can have completely different partition and sort keys — they can be created at any time and have their own read/write capacity.

# Create a DynamoDB table with GSI
aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=PK,AttributeType=S \
    AttributeName=SK,AttributeType=S \
    AttributeName=GSI1PK,AttributeType=S \
    AttributeName=GSI1SK,AttributeType=S \
  --key-schema \
    AttributeName=PK,KeyType=HASH \
    AttributeName=SK,KeyType=RANGE \
  --global-secondary-indexes \
    "[{\"IndexName\":\"GSI1\",\"KeySchema\":[{\"AttributeName\":\"GSI1PK\",\"KeyType\":\"HASH\"},{\"AttributeName\":\"GSI1SK\",\"KeyType\":\"RANGE\"}],\"Projection\":{\"ProjectionType\":\"ALL\"}}]" \
  --billing-mode PAY_PER_REQUEST

# Single-table design: PK/SK patterns
# User:       PK=USER#userId    SK=PROFILE
# Order:      PK=USER#userId    SK=ORDER#orderId
# GSI1 query: PK=ORDER#status   SK=createdAt (all orders by status)

# Query orders for a user
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "PK = :pk AND begins_with(SK, :skPrefix)" \
  --expression-attribute-values \
    "{\":pk\":{\"S\":\"USER#user123\"},\":skPrefix\":{\"S\":\"ORDER#\"}}"

# Query via GSI (all PENDING orders)
aws dynamodb query \
  --table-name Orders \
  --index-name GSI1 \
  --key-condition-expression "GSI1PK = :status" \
  --expression-attribute-values "{\":status\":{\"S\":\"ORDER#PENDING\"}}"

DynamoDB Streams

Streams capture a time-ordered sequence of every change (INSERT, MODIFY, REMOVE) made to items in a table. Each record is available for 24 hours. Streams can trigger Lambda functions for event-driven architectures.

CloudFront: Global Content Delivery Network

CloudFront is AWS's CDN with 450+ edge locations globally. It caches content close to users, reducing latency and origin load. CloudFront supports S3, EC2, ALB, API Gateway, and custom origins.

Cache Behaviors

Behaviors define how CloudFront handles requests for different URL patterns. You can configure different origins, TTLs, headers to forward, and Lambda@Edge functions for each behavior. The default behavior matches all requests (*) and is your fallback.

# Create a CloudFront distribution (S3 origin)
aws cloudfront create-distribution --distribution-config file://cf-config.json

# cf-config.json (simplified)
{
  "Origins": {
    "Quantity": 1,
    "Items": [{
      "Id": "S3Origin",
      "DomainName": "my-app-bucket.s3.amazonaws.com",
      "S3OriginConfig": { "OriginAccessIdentity": "" }
    }]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3Origin",
    "ViewerProtocolPolicy": "redirect-to-https",
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "Compress": true
  },
  "CacheBehaviors": {
    "Quantity": 1,
    "Items": [{
      "PathPattern": "/api/*",
      "TargetOriginId": "APIGatewayOrigin",
      "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
      "ViewerProtocolPolicy": "https-only"
    }]
  },
  "PriceClass": "PriceClass_100",
  "HttpVersion": "http2and3",
  "IsIPV6Enabled": true
}

Cache Invalidation

When you update content, you may need to invalidate the CloudFront cache. The first 1,000 invalidation paths per month are free; subsequent ones cost $0.005 each. Use versioned file names (app.v2.js) instead of invalidations for better cache control.

# Invalidate specific files
aws cloudfront create-invalidation \
  --distribution-id EDFDVBD6EXAMPLE \
  --paths "/index.html" "/app.js"

# Invalidate all files (expensive if > 1000 paths/month)
aws cloudfront create-invalidation \
  --distribution-id EDFDVBD6EXAMPLE \
  --paths "/*"

# Better: Use versioned asset names (no invalidation needed)
# app.abc123.js  <- content hash in filename
# styles.def456.css
# Only invalidate index.html (the entry point)

VPC: Virtual Private Cloud

VPC is your private, isolated network within AWS. Every AWS account comes with a default VPC. For production workloads, create custom VPCs with proper subnet segmentation.

Public vs Private Subnets

Public subnets have a route to the Internet Gateway and are used for resources that need direct internet access (load balancers, bastion hosts). Private subnets have no direct internet route and are used for databases, application servers, and Lambda functions. Private subnets can access the internet via a NAT Gateway (for IPv4) in a public subnet.

# Create a VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16

# Create public subnets (one per AZ)
aws ec2 create-subnet \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-east-1a

aws ec2 create-subnet \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.0.2.0/24 \
  --availability-zone us-east-1b

# Create private subnets
aws ec2 create-subnet \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.0.11.0/24 \
  --availability-zone us-east-1a

# Create Internet Gateway (for public subnets)
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway \
  --vpc-id vpc-0abc123 \
  --internet-gateway-id igw-0abc123

# Create NAT Gateway in public subnet (for private subnet outbound)
# First, allocate an Elastic IP
aws ec2 allocate-address --domain vpc

aws ec2 create-nat-gateway \
  --subnet-id subnet-public-1a \
  --allocation-id eipalloc-0abc123

# Add default route to private route table via NAT Gateway
aws ec2 create-route \
  --route-table-id rtb-private \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-0abc123

NACLs vs Security Groups

Network ACLs operate at the subnet level and are stateless: you must explicitly allow both inbound and outbound rules. Security groups operate at the instance level and are stateful. Use security groups as your primary control; use NACLs for additional subnet-level protection when needed.

CloudFormation: Infrastructure as Code

CloudFormation lets you provision AWS resources using JSON or YAML templates. It manages resource dependencies, supports rollback on failure, and enables infrastructure versioning. It is the AWS-native IaC tool; alternatives include CDK (TypeScript/Python), Terraform, and Pulumi.

# CloudFormation template: S3 bucket + Lambda + API Gateway
AWSTemplateFormatVersion: "2010-09-09"
Description: "Serverless API stack"

Parameters:
  Environment:
    Type: String
    AllowedValues: [dev, staging, production]
    Default: dev

Resources:
  # S3 bucket for uploads
  UploadsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "uploads-${Environment}-${AWS::AccountId}"
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  # Lambda execution role
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: { Service: lambda.amazonaws.com }
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: S3Access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: [s3:GetObject, s3:PutObject]
                Resource: !Sub "${UploadsBucket.Arn}/*"

  # Lambda function
  ApiFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "api-handler-${Environment}"
      Runtime: nodejs20.x
      Handler: dist/index.handler
      Role: !GetAtt LambdaRole.Arn
      Timeout: 30
      MemorySize: 256
      Environment:
        Variables:
          BUCKET_NAME: !Ref UploadsBucket
          ENV: !Ref Environment

Outputs:
  BucketName:
    Value: !Ref UploadsBucket
  FunctionArn:
    Value: !GetAtt ApiFunction.Arn

# Deploy the stack
aws cloudformation deploy \
  --template-file template.yml \
  --stack-name my-serverless-api \
  --parameter-overrides Environment=production \
  --capabilities CAPABILITY_IAM

AWS CLI: Key Commands and Profiles

The AWS CLI is essential for automation, scripting, and day-to-day operations. Install it with pip or a package manager, configure profiles, and use it in scripts and CI/CD pipelines.

# Install AWS CLI v2
# macOS
brew install awscli

# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install

# --- Configure profiles ---
# Interactive setup (asks for key ID, secret, region, output format)
aws configure

# Configure a named profile
aws configure --profile production

# ~/.aws/credentials
# [default]
# aws_access_key_id = AKIAIOSFODNN7EXAMPLE
# aws_secret_access_key = wJalrXUtnFEMI/K7MDENG...

# [production]
# aws_access_key_id = AKIAI...
# aws_secret_access_key = ...

# ~/.aws/config
# [default]
# region = us-east-1
# output = json

# [profile production]
# region = us-east-1
# role_arn = arn:aws:iam::123456789012:role/ProductionRole
# source_profile = default

# Use a specific profile
aws s3 ls --profile production
AWS_PROFILE=production aws ec2 describe-instances

# --- Essential commands ---
# EC2
aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==Name].Value|[0]]" --output table
aws ec2 start-instances --instance-ids i-0abc123
aws ec2 stop-instances --instance-ids i-0abc123

# S3
aws s3 ls s3://my-bucket/ --recursive --human-readable --summarize
aws s3 rm s3://my-bucket/old-file.txt
aws s3 mv s3://src/file.txt s3://dest/file.txt

# Lambda
aws lambda list-functions --query "Functions[].[FunctionName,Runtime,LastModified]" --output table
aws lambda invoke --function-name my-fn --payload "{\"test\":true}" response.json

# CloudWatch Logs
aws logs tail /aws/lambda/my-fn --follow
aws logs filter-log-events \
  --log-group-name /aws/lambda/my-fn \
  --filter-pattern "ERROR"

# SSM Parameter Store (store secrets securely)
aws ssm put-parameter \
  --name "/myapp/prod/db-password" \
  --value "supersecret" \
  --type SecureString

aws ssm get-parameter \
  --name "/myapp/prod/db-password" \
  --with-decryption \
  --query "Parameter.Value" --output text

AWS vs GCP vs Azure: Service Comparison

Here is a comparison of equivalent services across the three major cloud providers.

Service CategoryAWSGoogle Cloud (GCP)Microsoft Azure
Virtual MachinesEC2Compute EngineVirtual Machines
Object StorageS3Cloud Storage (GCS)Azure Blob Storage
Serverless FunctionsLambdaCloud Functions / Cloud RunAzure Functions
Managed KubernetesEKSGKEAKS
Managed MySQL/PostgresRDS AuroraCloud SQLAzure Database for PostgreSQL
NoSQL DatabaseDynamoDBFirestore / BigtableCosmos DB
CDNCloudFrontCloud CDNAzure CDN / Front Door
DNSRoute 53Cloud DNSAzure DNS
Message QueueSQSCloud Pub/SubAzure Service Bus
Event StreamingKinesisPub/Sub Lite / DataflowAzure Event Hubs
Container RegistryECRArtifact Registry (GCR)Azure Container Registry
Identity / AuthCognitoFirebase Auth / Identity PlatformAzure Active Directory B2C
API GatewayAPI GatewayApigee / Cloud EndpointsAzure API Management
Infrastructure as CodeCloudFormation / CDKDeployment Manager / Config ConnectorARM / Bicep
MonitoringCloudWatchCloud Monitoring + LoggingAzure Monitor
Secret ManagementSecrets ManagerSecret ManagerAzure Key Vault
ML/AI PlatformSageMakerVertex AIAzure Machine Learning
Data WarehouseRedshiftBigQueryAzure Synapse Analytics
VPN / Private NetworkVPC + Direct ConnectVPC + Dedicated InterconnectVNet + ExpressRoute

Cost Optimization Tips

AWS costs can spiral quickly without careful management. These strategies will help you control your bill.

  • Right-size EC2 instances: use AWS Compute Optimizer recommendations. Oversized instances are the most common source of waste.
  • Use Savings Plans or Reserved Instances for predictable workloads: 1-year no-upfront saves 30-40%, 3-year saves up to 72%.
  • Enable S3 Intelligent Tiering for objects accessed unpredictably — it automatically moves data to the cheapest tier.
  • Use Spot Instances for fault-tolerant workloads (batch processing, CI/CD, ML training) — up to 90% savings.
  • Set up AWS Budgets and billing alerts in CloudWatch immediately. A forgotten EC2 instance can cost hundreds per month.
  • Delete unused Elastic IP addresses, unattached EBS volumes, and idle load balancers — these cost money even when unused.
  • Use CloudFront to reduce data transfer costs from EC2/S3 — CloudFront to internet is cheaper than EC2 to internet.
  • Enable RDS Auto Stop for development databases — automatically stops them after a period of inactivity to avoid idle charges.
# Set up a billing alert at $50
aws cloudwatch put-metric-alarm \
  --alarm-name "AWS-Billing-50-USD" \
  --alarm-description "Alert when estimated charges exceed $50" \
  --namespace "AWS/Billing" \
  --metric-name "EstimatedCharges" \
  --statistic Maximum \
  --dimensions Name=Currency,Value=USD \
  --period 86400 \
  --evaluation-periods 1 \
  --threshold 50 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:billing-alerts

# Note: Billing metrics only exist in us-east-1!
# aws cloudwatch put-metric-alarm --region us-east-1 ...

# Find idle/unused EC2 instances (< 5% CPU over 7 days)
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-0abc123 \
  --start-time 2026-02-20T00:00:00Z \
  --end-time 2026-02-27T00:00:00Z \
  --period 86400 \
  --statistics Average

# List unattached EBS volumes (potential waste)
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query "Volumes[].[VolumeId,Size,CreateTime]" \
  --output table

# List unused Elastic IPs (cost $0.005/hour if unattached)
aws ec2 describe-addresses \
  --query "Addresses[?AssociationId==null].[AllocationId,PublicIp]" \
  --output table

Frequently Asked Questions

What is the difference between EC2 and Lambda?

EC2 provides virtual machines you manage (OS, dependencies, scaling). Lambda is serverless: you only write code, AWS handles the infrastructure. EC2 is better for long-running processes, stateful workloads, and when you need specific system configuration. Lambda is better for event-driven, short-lived tasks, and when you want zero infrastructure management and pay-per-use billing.

When should I use DynamoDB vs RDS?

Use DynamoDB when you need: massive scale (millions of requests/second), single-digit millisecond latency, a flexible schema, or when your access patterns are predictable and can be modeled with a partition key + sort key. Use RDS when you need: complex queries and joins, ACID transactions across multiple tables, a familiar SQL interface, or when your query patterns are complex and ad-hoc.

What is the cheapest way to run a static website on AWS?

S3 static website hosting + CloudFront CDN. Upload your HTML/CSS/JS to an S3 bucket, enable static hosting, create a CloudFront distribution pointing to S3, and use Route 53 for your domain. This setup costs pennies for low-traffic sites and scales to millions of users without any servers. The S3 Free Tier includes 5GB storage and 20,000 GET requests per month.

How do IAM roles work for EC2 instances?

When you attach an IAM role to an EC2 instance, the instance metadata service (IMDS) provides temporary credentials to applications running on the instance. The AWS SDK automatically reads these credentials from http://169.254.169.254/latest/meta-data/iam/security-credentials/ — you do not need to set any environment variables or config files. The credentials are automatically rotated. Never put AWS access keys in your application code or EC2 user data.

What is the difference between CloudFront and a load balancer?

CloudFront is a Content Delivery Network (CDN) that caches content at edge locations globally, reducing latency for geographically distributed users and reducing origin load. An Application Load Balancer (ALB) distributes traffic across multiple backend servers within a region for availability and scalability. Use both together: CloudFront in front of an ALB for global caching + regional load balancing. CloudFront is not a replacement for a load balancer.

How do S3 presigned URLs work?

A presigned URL is a URL with authentication parameters embedded in the query string (signature, expiration, access key ID). When a client requests this URL, S3 validates the signature and expiration without requiring any AWS credentials from the client. This is the standard pattern for: user file uploads (presigned PUT), secure file downloads (presigned GET), and third-party access to private objects without making the bucket public. Presigned URLs expire after a configured duration (15 minutes to 7 days).

What is VPC peering and when do I need it?

VPC peering creates a private network connection between two VPCs, allowing resources in either VPC to communicate using private IP addresses as if they were in the same network. You need it when you have resources in separate VPCs that need to communicate privately — for example, a shared services VPC (with databases, monitoring) connected to multiple application VPCs. Peering does not support transitive routing: if A peers with B and B peers with C, A cannot reach C through B.

What is the AWS Free Tier and what are its limits?

The AWS Free Tier has three types: 12-month free (for new accounts: 750 hours/month t2.micro EC2, 5GB S3, 25GB DynamoDB, 750 hours RDS db.t2.micro), Always free (Lambda: 1M requests/month, DynamoDB: 25GB, CloudFront: 1TB data transfer out, SNS: 1M publishes), and Trials (limited-time free trials for newer services). Be careful: the Free Tier does NOT cover all services, and exceeding limits results in charges. Always set up billing alerts.

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

{ }JSON FormatterB→Base64 Encoder%20URL Encoder/Decoder

Related Articles

Docker 명령어: 기초부터 프로덕션까지 완전 가이드

Docker를 마스터하세요. docker run/build/push, Dockerfile, 멀티스테이지 빌드, 볼륨, 네트워킹, Docker Compose, 보안, 레지스트리, 프로덕션 배포 완전 가이드.

Kubernetes 개발자 완전 가이드: Pod, Helm, RBAC, CI/CD

Kubernetes를 마스터하세요. Pod, Deployment, Service, Ingress, Helm, PVC, 헬스체크, HPA, RBAC, GitHub Actions CI/CD 통합 완전 가이드.

Terraform 완전 가이드: 기초부터 CI/CD까지의 Infrastructure as Code

Terraform IaC를 마스터하세요. HCL 구문, 모듈, 상태 관리, AWS 프로바이더, 워크스페이스, Terraform Cloud, tfsec/checkov/Terratest 테스트 완전 가이드.