AWS (reinforce)
תשתית ענן בשימוש אמיתי: Bastion, Secrets, PostgreSQL, FinOps ו-IAM
תיאוריה
למה AWS Reinforce עכשיו? — שוק ה-DevOps הישראלי
תדירות AWS במשרות DevOps ישראליות עומדת על 9.4 מתוך 10 — כמעט כל משרה בינונית ומעלה מצריכה ידע AWS מעשי. אבל הקורסים הסטנדרטיים מלמדים 'צור EC2, צור S3'. הפער האמיתי הוא בתשתית שמסביב:
- **SSH דרך bastion** — אף שרת production לא נגיש ישירות מהאינטרנט
- **ניהול סודות** — פרטי חיבור ב-AWS Secrets Manager ו-HashiCorp Vault, לא בקובץ .env
- **גיבוי PostgreSQL** — pg_dump + העברה ל-S3, כי managed RDS יקר לכולם
- **FinOps** — Cost Explorer + Reserved vs Spot, כי כסף ענן בוצא
- **IAM least-privilege** — כי כל breach גדול מתחיל ב-AdministratorAccess שמישהו שכח להסיר
זהו הנושא עם ה-market_frequency הגבוה ביותר בסמסטר הזה. כל הפערים בסעיף זה מגיעים מניתוח משרות ריאליות ממאגר job_report.
SSH Jump Hosts ו-ProxyJump — גישה לשרתים בסאבנט פרטי
כל ארכיטקטורת production מכניסה שרתי application לסאבנט פרטי ללא IP ציבורי. הדרך הנכונה להגיע אליהם היא דרך **bastion host** — שרת ייעודי עם IP ציבורי שמשמש כ-jump point.
**שיטה 1 — ProxyJump בשורת הפקודה:**
ssh -J ubuntu@bastion.example.com ec2-user@10.0.1.50**שיטה 2 — הגדרת ~/.ssh/config (הדרך הנכונה לproduction):**
Host bastion
HostName 54.200.1.100
User ubuntu
IdentityFile ~/.ssh/bastion_key.pem
ServerAliveInterval 60
Host app-server
HostName 10.0.1.50
User ec2-user
IdentityFile ~/.ssh/app_key.pem
ProxyJump bastionאחרי ההגדרה:
ssh app-server # עובר אוטומטית דרך bastion
scp file.tar.gz app-server:/tmp/ # גם scp עובר דרך ProxyJump**ProxyCommand — שיטה עבור AWS SSM (בלי SSH פתוח לאינטרנט):**
Host i-*
ProxyCommand aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'
User ec2-user
IdentityFile ~/.ssh/app_key.pem**סיכוני agent forwarding** — לעולם אל תשתמש ב-ssh -A אל bastion בלי הבנה מלאה. אם ה-bastion נפרץ, כל ה-agent keys נחשפות. העדף ProxyJump שלא מוסר credentials ל-bastion.
AWS Secrets Manager — ניהול סודות בענן
הטעות הנפוצה ביותר היא אחסון passwords ב-.env files ב-git. AWS Secrets Manager פותר זאת: הצפנת KMS, rotation אוטומטי, ו-audit trail ב-CloudTrail.
**יצירת secret:**
aws secretsmanager create-secret \
--name prod/myapp/db-password \
--description "PostgreSQL production password" \
--secret-string '{"username":"dbadmin","password":"s3cr3t!"}'**קריאת secret בסקריפט:**
SECRET=$(aws secretsmanager get-secret-value \
--secret-id prod/myapp/db-password \
--query SecretString \
--output text)
DB_PASS=$(echo "$SECRET" | python3 -c "import sys,json; print(json.load(sys.stdin)['password'])")
export PGPASSWORD="$DB_PASS"**Rotation אוטומטי — 30 יום:**
aws secretsmanager rotate-secret \
--secret-id prod/myapp/db-password \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789:function:SecretsRotation \
--rotation-rules AutomaticallyAfterDays=30**IAM Policy מינימלית לאפליקציה:**
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:prod/myapp/*"
}]
}חשוב: הגבל גישה ל-secret ספציפי (prod/myapp/*) ולא ל-* כללי.
HashiCorp Vault — ניהול סודות דינמי
Vault הוא הפתרון הפופולרי ב-on-premise ובמצבים שבהם AWS Secrets Manager אינו מספיק — בעיקר כשצריך **dynamic secrets** (credentials שנוצרים ונמחקים per-request).
**הפקודות הבסיסיות:**
# אתחול (פעם אחת)
vault operator init
vault operator unseal <key1>
vault operator unseal <key2>
# כתיבת secret
vault kv put secret/myapp/db \
username=dbadmin \
password=s3cr3t!
# קריאת secret
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db**AppRole Authentication — לאפליקציות production:**
# Enable AppRole
vault auth enable approle
# יצירת role לאפליקציה
vault write auth/approle/role/myapp-role \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=24h
# הוצאת credentials לאפליקציה
ROLE_ID=$(vault read -field=role_id auth/approle/role/myapp-role/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/myapp-role/secret-id)
# Login
vault write auth/approle/login role_id=$ROLE_ID secret_id=$SECRET_ID**Dynamic Secrets לPostgreSQL — הנשק הסודי:**
vault secrets enable database
vault write database/config/myapp-db \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-role" \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb"
vault write database/roles/myapp-role \
db_name=myapp-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'" \
default_ttl=1h max_ttl=24h
# כל קריאה מחזירה credentials חדשים שתקפים שעה
vault read database/creds/myapp-roleבמצב זה, לאפליקציה אין סיסמה קבועה — כל instance מקבל credentials ייחודיים עם TTL מוגדר.
pg_dump — גיבוי PostgreSQL ו-העברה ל-S3
Managed RDS עולה כסף. אפליקציות רבות רצות על PostgreSQL עצמאי בEC2 או VPS, וגיבוי הוא אחריות המהנדס.
**פקודת pg_dump עם כל הפלאגים הנכונים:**
pg_dump \
--host=localhost \
--port=5432 \
--username=backup_user \
--format=custom \
--compress=9 \
--no-owner \
--no-acl \
--verbose \
myapp_db > /backups/myapp_$(date +%Y%m%d_%H%M%S).dump**הסבר הפלאגים:**
- --format=custom — פורמט בינארי עם דחיסה מובנית, תומך ב-parallel restore
- --compress=9 — דחיסה מקסימלית (פחות קריטי עם --format=custom)
- --no-owner — לא שומר ownership (נכון לrestore בסביבה אחרת)
- --no-acl — לא שומר GRANT/REVOKE permissions
**סקריפט גיבוי מלא עם העברה ל-S3:**
#!/usr/bin/env bash
set -euo pipefail
DB_NAME="myapp_db"
BACKUP_DIR="/backups"
S3_BUCKET="s3://my-company-backups/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.dump"
RETENTION_DAYS=7
pg_dump \
--host=localhost \
--username=backup_user \
--format=custom \
--no-owner \
--no-acl \
"${DB_NAME}" > "${FILE}"
gzip "${FILE}"
aws s3 cp "${FILE}.gz" "${S3_BUCKET}/" \
--sse aws:kms \
--storage-class STANDARD_IA
find "${BACKUP_DIR}" -name "*.gz" -mtime +${RETENTION_DAYS} -delete
echo "Backup complete: ${FILE}.gz uploaded to ${S3_BUCKET}"**הוספה ל-cron (גיבוי כל לילה בשעה 2:00):**
0 2 * * * /opt/scripts/backup_postgres.sh >> /var/log/postgres_backup.log 2>&1**שחזור מgzip dump:**
gunzip -c myapp_20240115_020000.dump.gz | \
pg_restore --host=localhost --username=dbadmin --dbname=myapp_db --cleanPostgreSQL Point-in-Time Recovery ו-WAL Archiving
pg_dump מגבה רק snapshot. אם יש 100,000 transactions ביום ומישהו מוחק טבלה בשעה 14:35 — הגיבוי מהשעה 02:00 לא מספיק. **WAL archiving** מאפשר שחזור לכל שנייה.
**מה זה WAL?**
Write-Ahead Log — PostgreSQL כותב כל שינוי ל-WAL לפני שהוא מגיע לדיסק. WAL archiving שומר את הקבצים האלה (בד"כ 16MB כל אחד) ב-S3 או NFS.
**הגדרת WAL archiving ב-postgresql.conf:**
wal_level = replica
archive_mode = on
archive_command = 'aws s3 cp %p s3://my-backups/wal/%f'
archive_timeout = 300
max_wal_senders = 3**שיחזור Point-in-Time (recovery.conf / postgresql.conf בגרסאות חדשות):**
restore_command = 'aws s3 cp s3://my-backups/wal/%f %p'
recovery_target_time = '2024-01-15 14:34:59'
recovery_target_action = promote**תהליך שחזור מלא:**
# 1. עצור PostgreSQL
systemctl stop postgresql
# 2. נקה data directory
rm -rf /var/lib/postgresql/14/main/*
# 3. שחזר את ה-base backup
pg_basebackup -h s3-source -D /var/lib/postgresql/14/main
# 4. הוסף קובץ recovery.conf / postgresql.conf עם recovery_target_time
cat > /var/lib/postgresql/14/main/recovery.signal << 'EOF'
EOF
# 5. הפעל PostgreSQL — יתחיל לhאחזר WAL עד לזמן שנקבע
systemctl start postgresql**כלי מקצועי — pgBackRest:**
pgbackrest --stanza=myapp backup --type=full
pgbackrest --stanza=myapp restore --target='2024-01-15 14:34:59' --target-action=promoteבפרקטיקה: WAL archiving חיוני לכל database עם SLA של RPO < 1 שעה.
PgBouncer — Connection Pooling לPostgreSQL
PostgreSQL יוצר process נפרד לכל connection. בעומס של 500 connections בו-זמנית, הserver שורף CPU ו-RAM רק על ניהול connections — לפני שמעבד שאילתה אחת.
**PgBouncer** יושב בין האפליקציה לבסיס הנתונים ומשתף connections:
App (5000 connections) → PgBouncer → PostgreSQL (20 connections)**שלושת מצבי ה-pooling:**
| מצב | מתי | עלות |
|-----|-----|------|
| session | connection של PgBouncer מוחזק כל session | נמוכה, ללא side effects |
| transaction | connection מוחזר לpool אחרי כל transaction | ביצועים גבוהים, SET LOCAL לא נשמר |
| statement | connection מוחזר אחרי כל statement | לא מומלץ, שובר transactions |
**/etc/pgbouncer/pgbouncer.ini:**
[databases]
myapp_db = host=127.0.0.1 port=5432 dbname=myapp_db
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600
log_connections = 0
log_disconnections = 0**/etc/pgbouncer/userlist.txt:**
"myapp_user" "md5_hashed_password_here"**האפליקציה מתחברת לport 6432 (PgBouncer) במקום 5432:**
DATABASE_URL=postgresql://myapp_user:password@127.0.0.1:6432/myapp_db**ניטור PgBouncer:**
# התחבר ל-pgbouncer console
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer
# סטטיסטיקות pool
SHOW POOLS;
SHOW CLIENTS;
SHOW SERVERS;
SHOW STATS;pg_stat_statements ו-EXPLAIN ANALYZE — זיהוי שאילתות איטיות
90% מבעיות הביצועים של אפליקציות מקורן בשאילתות בסיס נתונים איטיות. שני הכלים הקריטיים:
**הפעלת pg_stat_statements:**
-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
-- אחרי restart
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;**שאילתות שזהות את ה-top offenders:**
-- TOP 10 שאילתות איטיות ביותר לפי זמן ממוצע
SELECT
query,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS avg_ms,
round(stddev_exec_time::numeric, 2) AS stddev_ms,
rows
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- שאילתות עם הכי הרבה קריאות לדיסק
SELECT query, shared_blks_read
FROM pg_stat_statements
ORDER BY shared_blks_read DESC
LIMIT 5;**EXPLAIN ANALYZE — ניתוח תוכנית ביצוע:**
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.created_at > '2024-01-01'
GROUP BY u.name
ORDER BY order_count DESC
LIMIT 10;**קריאת פלט EXPLAIN — הנקודות הקריטיות:**
Sort (cost=1234.56..1235.06 rows=200 width=40) (actual time=89.123..89.234 rows=10 loops=1)
-> HashAggregate (cost=1224.56..1226.56 rows=200 width=40) (actual time=88.900..89.050 rows=200 loops=1)
-> Hash Join (cost=500.00..1100.00 rows=4990 width=32) (actual time=10.500..75.000 rows=4990 loops=1)
-> Seq Scan on orders (cost=0.00..450.00 rows=10000 width=16) # <-- בעיה!
-> Hash (cost=300.00..300.00 rows=5000 width=20) (actual time=8.000..8.000 rows=5000 loops=1)
-> Index Scan using idx_users_created_at on users (cost=0.00..300.00 rows=5000)
Execution Time: 89.456 ms**Seq Scan הוא הסימן לאינדקס חסר:**
-- יצירת אינדקס שיפתור את ה-Seq Scan על orders
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);
-- CONCURRENTLY — לא נועל את הטבלה בproductionFinOps — AWS Cost Explorer, Reserved Instances ו-Spot
AWS חשבונות גדלים בשקט. חברות מגלות עלויות מפתיעות רק כשמגיע החשבון החודשי. FinOps הוא מקצוע שלם — הנה הבסיס שכל DevOps Engineer חייב לדעת.
**AWS Cost Explorer — CLI:**
# עלות חודש שעבר לפי service
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-02-01 \
--granularity MONTHLY \
--metrics "BlendedCost" \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[*].[Keys[0],Metrics.BlendedCost.Amount]' \
--output table
# Top 5 resources היקרים ביותר
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-02-01 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=RESOURCE_ID \
--filter file://cost_filter.json**Reserved Instances vs Spot vs On-Demand:**
| סוג | חיסכון | מתאים ל |
|-----|--------|----------|
| On-Demand | 0% (baseline) | dev, unpredictable |
| Reserved (1yr, no upfront) | ~30% | production baseline load |
| Reserved (3yr, all upfront) | ~60% | stable, long-running workloads |
| Spot Instances | ~70-90% | batch jobs, CI runners, stateless workers |
| Savings Plans | ~20-66% | flexible compute commitment |
**Spot Instances בGitHub Actions CI:**
# Spot-based CI runner — 80% חיסכון
- name: Launch Spot Runner
run: |
aws ec2 request-spot-instances \
--spot-price "0.05" \
--instance-count 1 \
--type "one-time" \
--launch-specification file://spot-spec.json**AWS Trusted Advisor — alerts על בזבוז:**
awsaws support describe-trusted-advisor-checks --language en \
--query 'checks[?category==`cost_optimizing`].[name,id]' \
--output table**Tags חובה לכל resource:**
aws ec2 create-tags \
--resources i-1234567890abcdef0 \
--tags Key=Environment,Value=production Key=Team,Value=backend Key=CostCenter,Value=engineeringIAM Least-Privilege — Policy Simulation, Conditions ו-SCPs
IAM הוא שכבת ההגנה הראשונה והאחרונה ב-AWS. הרוב מתחילים עם AdministratorAccess ו'ינקו אחר כך' — אחר כך לעולם לא מגיע.
**עיקרון Least-Privilege בפרקטיקה:**
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3BackupBucketOnly",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-company-backups",
"arn:aws:s3:::my-company-backups/*"
]
},
{
"Sid": "DenyDeleteUnlessTagged",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::my-company-backups/*",
"Condition": {
"StringNotEquals": {
"s3:ExistingObjectTag/deletable": "true"
}
}
}
]
}**Policy Simulator — בדיקת policy לפני deploy:**
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789:user/backup-script \
--action-names s3:PutObject s3:DeleteObject \
--resource-arns arn:aws:s3:::my-company-backups/test.dump \
--query 'EvaluationResults[*].[EvalActionName,EvalDecision]' \
--output table**SCPs — Service Control Policies (ברמת Organization):**
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRegionsOutsideIL",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["eu-west-1", "il-central-1"]
}
}
},
{
"Sid": "DenyRootAccountUsage",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}**Conditions שחייבים להיות בכל production policy:**
- aws:RequestedRegion — מונע יצירת resources באזורים לא מורשים
- aws:MultiFactorAuthPresent — חובת MFA לפעולות הרסניות
- ec2:ResourceTag/Environment — מגביל פעולות לresources עם tag מסוים
**בדיקת IAM מהיר — מי יש לו AdministratorAccess:**
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--query '[PolicyUsers[*].UserName, PolicyGroups[*].GroupName, PolicyRoles[*].RoleName]'תרגול מעשי
1.
2.
3.
שאלות חיבור
חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.
שאלת חיבור 1
הגדרת ProxyJump ב-~/.ssh/config (מ-Linux Advanced) נחוצה כדי להגיע לשרתי application בסאבנט פרטי ב-AWS. הסבר: (1) מה ה-connection flow המלא מהמחשב שלך לשרת app ב-private subnet דרך bastion, (2) למה AWS SSM Session Manager מהווה חלופה ל-SSH פתוח לאינטרנט, ו-(3) מה הסיכון ב-ssh -A (agent forwarding) שלמדנו להימנע ממנו.
שאלת חיבור 2
בPython for DevOps לימדנו שימוש ב-boto3 ו-os.environ. כיצד משלבים את שניהם עם AWS Secrets Manager בסביבת production? הסבר: (1) כיצד boto3 קורא secret מ-Secrets Manager ב-Python תוך שימוש נכון בos.environ לאזור ה-AWS, (2) מדוע os.environ עדיף על hardcoded region string, ו-(3) מה הpath הנכון ל-secret name (naming convention) שמקשה על שגיאות ב-production.
מוכן לבחינה?
בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.