Goorm ํ๋กํํธ ํด๋ผ์ฐ๋ ์์ง๋์ด๋ง ๊ณผ์ 3๊ธฐ โ 2์ฐจ ํ๋ก์ ํธ
Eat Cloud๋ '๋ฐฐ๋ฌ์ ๋ฏผ์กฑ'์ ๋ฒค์น๋งํนํ ์ฃผ๋ฌธ ๊ด๋ฆฌ ํ๋ซํผ์
๋๋ค.
1์ฐจ ํ๋ก์ ํธ์์ ๋ง๋ ๋ชจ๋๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ
์ฒ๋ก ์ ํํ๊ณ ๊ทธ์ ๋ง๋ ์ธํ๋ผ๊น์ง ๊ตฌ์ถํ์ต๋๋ค.
- 25.08.07 ~ 25.08.24
Java Spring Boot Spring Security PostgreSQL PostGIS Redis QueryDSL Spring Cloud Netflix Eureka Rest Template AWS GitHub Actions ECS Docker MSK RDS
- ๊ฐ ๋๋ฉ์ธ๋ณ๋ก ๋ง์ดํฌ๋ก์๋น์ค ๋ถ๋ฆฌ
- Spring Cloud Netflix Eureka๋ฅผ ํตํ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ
- API Gateway๋ฅผ ํตํ ๋ผ์ฐํ
- Passport ๊ตฌ์กฐ ๋ด๋ถ ํ ํฐ ์ธ์ฆ/์ธ๊ฐ
- Redis ํ์ฉ ์ฅ๋ฐ๊ตฌ๋ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐฐ์น ์ฒ๋ฆฌ
- TF-IDF ์ ์ฌ๋ ํ์ฉ ์ฌ์ฉ์ ๊ฒ์ ๊ธฐ๋ฐ ๋ฉ๋ด ์ถ์ฒ
- GitHub Actions CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
- AWS ECS Fargate ์๋ฒ๋ฆฌ์ค ์ด์ํ๊ฒฝ ๊ตฌ์ถ
- ๊ณตํต ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชจ๋ํ
- ํ๊ฒฝ๋ณ ์ค์ ๊ด๋ฆฌ
- Prometheus, Loki, Grafana๋ก ๋ชจ๋ํฐ๋ง
profect-eatcloud-msa-v1/
โโโ ๐ ๊ณตํต ๋ชจ๋
โ โโโ auto-response/ # ๊ณตํต ์๋ต/์์ธ ์ฒ๋ฆฌ ๋ชจ๋
โ โโโ auto-time/ # ๊ณตํต ์๊ฐ ๊ด๋ฆฌ ๋ชจ๋
โ
โโโ ๐ ์ธํ๋ผ ์๋น์ค
โ โโโ eureka-server/ # ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ
โ โโโ api-gateway/ # API ๊ฒ์ดํธ์จ์ด
โ
โโโ ๐ ํต์ฌ ๋น์ฆ๋์ค ์๋น์ค
โ โโโ auth-service/ # ์ธ์ฆ/์ธ๊ฐ ์๋น์ค
โ โโโ customer-service/ # ๊ณ ๊ฐ ๊ด๋ฆฌ ์๋น์ค
โ โโโ store-service/ # ๋งค์ฅ/๋ฉ๋ด ๊ด๋ฆฌ ์๋น์ค
โ โโโ order-service/ # ์ฃผ๋ฌธ ๊ด๋ฆฌ ์๋น์ค
โ โโโ payment-service/ # ๊ฒฐ์ ์ฒ๋ฆฌ ์๋น์ค
โ โโโ admin-service/ # ๊ด๋ฆฌ์ ์๋น์ค
โ โโโ manager-service/ # ๋งค๋์ ์๋น์ค
โ
โโโ ๐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ
โ โโโ database-init/ # SQL ์คํค๋ง ๋ฐ ๋ฐ์ดํฐ ์ด๊ธฐํ
โ
โโโ ๐ ๋ฐฐํฌ ๋ฐ ํ๊ฒฝ ์ค์
โโโ deploy/
โ โโโcompose/ # Docker Compose ์ค์
โโโ docker-compose.yml # ๋ฉ์ธ Docker Compose
- ๋ฐฐํฌ ์๊ฐ 58% ๋จ์ถ: 1์๊ฐ โ 25๋ถ
- ์ค๋ณต ์ฝ๋ 80% ์ ๊ฑฐ: auto-time/auto-response ๊ณตํต ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐฐํฌ
- ๋ฌด์ค๋จ ๋ฐฐํฌ: ECS Blue/Green ๋ฐฐํฌ + ์๋ ๋กค๋ฐฑ
- ๋ณด์ ๊ฐํ: AWS Secrets Manager๋ก ๋ฏผ๊ฐ์ ๋ณด ์ค์ ๊ด๋ฆฌ
7๊ฐ ๋ง์ดํฌ๋ก์๋น์ค๋ฅผ ์์ฐจ์ ์ผ๋ก ๋น๋/๋ฐฐํฌํ๋ฉด 1์๊ฐ ์ด์ ์์๋์ด ๊ฐ๋ฐ ์์ฐ์ฑ์ด ์ ํ๋์์ต๋๋ค.
๋ณ๊ฒฝ๋ ์๋น์ค๋ง ์๋์ผ๋ก ๊ฐ์งํ์ฌ ์ ํ์ ๋ฐฐํฌ๋ฅผ ์ํํฉ๋๋ค.
# .github/workflows/aws.yml
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.changes.outputs.services }}
matrix: ${{ steps.changes.outputs.matrix }}
steps:
- name: Detect changed services
id: changes
run: |
# Git diff๋ก ๋ณ๊ฒฝ๋ ํ์ผ ํ์ง
changed_files=$(git diff --name-only HEAD~1 HEAD)
services=()
# ๊ฐ ์๋น์ค ๋๋ ํ ๋ฆฌ ํ์ธ
for service in auth-service customer-service admin-service \
manager-service store-service order-service payment-service; do
if echo "$changed_files" | grep -q "^$service/"; then
services+=("$service")
fi
done
# ๊ณตํต ํ์ผ ๋ณ๊ฒฝ ์ ์ ์ฒด ๋ฐฐํฌ
if echo "$changed_files" | grep -qE "^(build\.gradle|settings\.gradle)"; then
services=(auth-service customer-service admin-service \
manager-service store-service order-service payment-service)
fi
# Matrix ์ ๋ต์ฉ JSON ์์ฑ
services_json=$(printf '%s\n' "${services[@]}" | jq -R -s -c 'split("\n")[:-1]')
echo "services=$services_json" >> $GITHUB_OUTPUTํจ๊ณผ:
- โ ๋ณ๊ฒฝ๋ ์๋น์ค๋ง ์ ํ์ ๋ฐฐํฌ
- โ ๋ถํ์ํ ๋น๋/๋ฐฐํฌ ์๊ฐ ์ ๊ฑฐ
- โ ๊ณตํต ํ์ผ ๋ณ๊ฒฝ ์ ์ ์ฒด ๋ฐฐํฌ ์๋ ๊ฐ์ง
GitHub Actions์ Matrix ์ ๋ต์ผ๋ก ์ฌ๋ฌ ์๋น์ค๋ฅผ ๋์์ ๋น๋/๋ฐฐํฌํฉ๋๋ค.
test-and-build:
needs: detect-changes
runs-on: ubuntu-latest
strategy:
matrix: ${{fromJson(needs.detect-changes.outputs.matrix)}}
fail-fast: false # ํ๋ ์คํจํด๋ ๋๋จธ์ง ๊ณ์ ์งํ
steps:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
- name: Test ${{ matrix.service }}
run: ./gradlew :${{ matrix.service }}:test
- name: Build ${{ matrix.service }}
run: ./gradlew :${{ matrix.service }}:bootJar
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: jar-${{ matrix.service }}-${{ github.sha }}
path: ${{ matrix.service }}/build/libs/*.jarํจ๊ณผ:
- โ 7๊ฐ ์๋น์ค ๋์ ๋น๋/ํ ์คํธ
- โ Gradle ์บ์๋ก ๋น๋ ์๊ฐ ๋จ์ถ
- โ ํ๋ ์คํจํด๋ ๋ค๋ฅธ ์๋น์ค๋ ๊ณ์ ์งํ
build-and-push-ecr:
needs: [detect-changes, test-and-build]
runs-on: ubuntu-latest
strategy:
matrix: ${{fromJson(needs.detect-changes.outputs.matrix)}}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.service }}/Dockerfile
push: true
tags: |
${{ env.ECR_REGISTRY }}/eatcloud/${{ matrix.service }}:latest
${{ env.ECR_REGISTRY }}/eatcloud/${{ matrix.service }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/arm64ํจ๊ณผ:
- โ ๋ฉํฐ ํ๊ทธ ์ ๋ต (latest, commit SHA)
- โ GitHub Actions ์บ์๋ก ๋น๋ ์๋ ํฅ์
- โ ARM64 ์ํคํ ์ฒ ์ง์ (๋น์ฉ ์ ๊ฐ)
graph LR
A[Git Push] --> B[๋ณ๊ฒฝ ๊ฐ์ง]
B --> C{๋ณ๊ฒฝ๋ ์๋น์ค?}
C -->|์์| D[Matrix ๋ณ๋ ฌ ๋น๋]
C -->|์์| E[๋ฐฐํฌ ์คํต]
D --> F[ํ
์คํธ ์คํ]
F --> G[JAR ๋น๋]
G --> H[Docker ์ด๋ฏธ์ง ๋น๋]
H --> I[ECR ํธ์]
I --> J[ECS ๋ฐฐํฌ]
J --> K[Blue/Green ์ ํ]
K --> L[ํฌ์ค์ฒดํฌ]
L -->|์ฑ๊ณต| M[๋ฐฐํฌ ์๋ฃ]
L -->|์คํจ| N[์๋ ๋กค๋ฐฑ]
style A fill:#e1f5ff
style D fill:#fff4e1
style M fill:#e1ffe1
style N fill:#ffe1e1
- โ ๋ฐฐํฌ ์๊ฐ: 1์๊ฐ โ 25๋ถ (58% ๋จ์ถ)
- โ ๋ณ๋ ฌ ์ฒ๋ฆฌ: 7๊ฐ ์๋น์ค ๋์ ๋น๋/๋ฐฐํฌ
- โ ์ ํ์ ๋ฐฐํฌ: ๋ณ๊ฒฝ๋ ์๋น์ค๋ง ์๋ ๊ฐ์ง
- โ ์บ์ ์ ๋ต: Gradle + Docker ๋ ์ด์ด ์บ์ฑ
๊ธฐ์กด ๋ฐฐํฌ ๋ฐฉ์์ ์๋น์ค ์ค๋จ ์๊ฐ์ด ๋ฐ์ํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํ์์ผฐ์ต๋๋ค.
์ด๋ฏธ์ง๊ฐ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ์๋ง ์๋ก์ด Task Definition์ ์์ฑํฉ๋๋ค.
- name: Smart Task Definition Update
id: task-def
run: |
TASK_DEF_FAMILY="eatcloud-${{ matrix.service }}"
NEW_IMAGE="${{ env.ECR_REGISTRY }}/eatcloud/${{ matrix.service }}:${{ github.sha }}"
# ํ์ฌ Task Definition ์กฐํ
CURRENT_TASK_DEF=$(aws ecs describe-services \
--cluster eatcloud-cluster \
--services ${{ matrix.service }} \
--query 'services[0].taskDefinition' \
--output text)
# ํ์ฌ ์ด๋ฏธ์ง ํ์ธ
CURRENT_IMAGE=$(aws ecs describe-task-definition \
--task-definition "$CURRENT_TASK_DEF" \
--query 'taskDefinition.containerDefinitions[0].image' \
--output text)
if [ "$CURRENT_IMAGE" != "$NEW_IMAGE" ]; then
echo "Image changed, creating new task definition..."
# ์ Task Definition ์์ฑ
TASK_DEF_JSON=$(aws ecs describe-task-definition \
--task-definition "$CURRENT_TASK_DEF" \
--query 'taskDefinition' --output json)
echo "$TASK_DEF_JSON" | jq --arg IMAGE "$NEW_IMAGE" \
'.containerDefinitions[0].image = $IMAGE' > new-task-def.json
NEW_TASK_DEF_ARN=$(aws ecs register-task-definition \
--cli-input-json file://new-task-def.json \
--query 'taskDefinition.taskDefinitionArn' --output text)
echo "NEW_TASK_DEF_ARN=$NEW_TASK_DEF_ARN" >> $GITHUB_OUTPUT
else
echo "Image unchanged, force deployment..."
echo "NEW_TASK_DEF_ARN=$CURRENT_TASK_DEF" >> $GITHUB_OUTPUT
fi- name: Start ECS Blue/Green Deployment
run: |
aws ecs update-service \
--cluster eatcloud-cluster \
--service ${{ matrix.service }} \
--task-definition ${{ steps.task-def.outputs.NEW_TASK_DEF_ARN }}
echo "Blue/Green deployment initiated"- name: Monitor Deployment Progress
run: |
for i in {1..40}; do
SERVICE_STATUS=$(aws ecs describe-services \
--cluster eatcloud-cluster \
--services ${{ matrix.service }} \
--query 'services[0]' \
--output json)
RUNNING_COUNT=$(echo "$SERVICE_STATUS" | jq -r '.runningCount')
DESIRED_COUNT=$(echo "$SERVICE_STATUS" | jq -r '.desiredCount')
# ์์ ํ ํ์ธ
STABLE_DEPLOYMENTS=$(echo "$SERVICE_STATUS" | jq -r \
'.deployments | map(select(.status == "PRIMARY")) | length')
if [ "$STABLE_DEPLOYMENTS" = "1" ] && \
[ "$RUNNING_COUNT" = "$DESIRED_COUNT" ]; then
echo "Deployment reached stable state!"
break
fi
sleep 30
done- name: Verify Deployment Results
run: |
# Target Group ํฌ์ค ์ํ ํ์ธ
TARGET_GROUPS=$(aws elbv2 describe-target-groups \
--query "TargetGroups[?contains(TargetGroupName, '${{ matrix.service }}')]" \
--output json)
for tg_arn in $(echo "$TARGET_GROUPS" | jq -r '.[].Arn'); do
HEALTHY_COUNT=$(aws elbv2 describe-target-health \
--target-group-arn "$tg_arn" \
--query 'length(TargetHealthDescriptions[?TargetHealth.State==`healthy`])' \
--output text)
echo "Healthy targets: $HEALTHY_COUNT"
done
if [ "$HEALTHY_COUNT" -gt 0 ]; then
echo "Deployment completed successfully!"
else
echo "Deployment verification failed"
exit 1
fisequenceDiagram
participant GHA as GitHub Actions
participant ECS as ECS Service
participant ALB as Application Load Balancer
participant Blue as Blue Environment
participant Green as Green Environment
GHA->>ECS: ์ Task Definition ๋ฑ๋ก
ECS->>Green: Green Environment์ ์ Task ์์ฑ
Green->>Green: ์ปจํ
์ด๋ ์์ ๋ฐ ํฌ์ค์ฒดํฌ
Green-->>ALB: ํฌ์ค์ฒดํฌ ์ฑ๊ณต ๋ณด๊ณ
ALB->>ALB: Green์ Target Group์ ๋ฑ๋ก
alt ํฌ์ค์ฒดํฌ ์ฑ๊ณต
ALB->>Green: ํธ๋ํฝ ์ ํ ์์
ALB->>Blue: ํธ๋ํฝ ๋น์จ ๊ฐ์
ALB->>Green: ํธ๋ํฝ 100% ์ ํ
ECS->>Blue: ์ด์ Task ์ข
๋ฃ
GHA->>GHA: ๋ฐฐํฌ ์ฑ๊ณต
else ํฌ์ค์ฒดํฌ ์คํจ
ALB->>Blue: ํธ๋ํฝ ์ ์ง
ECS->>Green: ์ Task ์ข
๋ฃ
GHA->>GHA: ์๋ ๋กค๋ฐฑ
end
- โ ๋ฌด์ค๋จ ๋ฐฐํฌ: Blue/Green ์ ํ์ผ๋ก ๋ค์ดํ์ 0์ด
- โ ์๋ ๋กค๋ฐฑ: ํฌ์ค์ฒดํฌ ์คํจ ์ ์ฆ์ ์ด์ ๋ฒ์ ์ผ๋ก ๋ณต๊ตฌ
- โ ๋ฐฐํฌ ์์ ์ฑ: Target Group ํฌ์ค์ฒดํฌ ์๋ ๊ฒ์ฆ
- โ ์ฌ์ฉ์ ๊ฒฝํ: ์๋น์ค ์ค๋จ ์์ด ์ ๋ฒ์ ๋ฐฐํฌ
๋ชจ๋ ๋ง์ดํฌ๋ก์๋น์ค์์ ์ค๋ณต๋ ์ฝ๋(์๋ต ํฌ๋งท, ์๊ฐ ๊ด๋ฆฌ)๊ฐ ๋ฐ๋ณต๋์ด ์ ์ง๋ณด์๊ฐ ์ด๋ ค์ ์ต๋๋ค.
๋ชจ๋ API ์๋ต์ ์ผ๊ด๋ ํฌ๋งท์ผ๋ก ์๋ ๋ณํํฉ๋๋ค.
// auto-response/src/main/java/com/eatcloud/autoresponse
@Configuration
public class AutoResponseConfiguration {
@Bean
public ResponseBodyAdvice<Object> apiResponseAdvice() {
return new ApiResponseAdvice();
}
@RestControllerAdvice
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, ...) {
if (body instanceof ApiResponse) {
return body;
}
return ApiResponse.success(body);
}
}
}์ฌ์ฉ ๋ฐฉ๋ฒ:
// ๊ฐ ๋ง์ดํฌ๋ก์๋น์ค์ build.gradle
dependencies {
implementation project(':auto-response')
}// ์๋น์ค ์ฝ๋์์ ์๋ ์ ์ฉ
@RestController
public class CustomerController {
@GetMapping("/customers/{id}")
public Customer getCustomer(@PathVariable UUID id) {
return customerService.findById(id);
// ์๋์ผ๋ก ApiResponse๋ก ๊ฐ์ธ์ง
}
}์ํฐํฐ์ ์์ฑ/์์ ์๊ฐ๊ณผ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค.
// auto-time/src/main/java/com/eatcloud/autotime
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}์ฌ์ฉ ๋ฐฉ๋ฒ:
dependencies {
implementation project(':auto-time')
}// ๊ฐ ์ํฐํฐ์์ ์์๋ง ํ๋ฉด ์๋ ์ ์ฉ
@Entity
public class Customer extends BaseTimeEntity {
@Id
private UUID id;
private String name;
// createdAt, updatedAt, createdBy, updatedBy ์๋ ๊ด๋ฆฌ
}- โ ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ: 80% ๊ฐ์
- โ ์ผ๊ด์ฑ: ๋ชจ๋ ์๋น์ค์์ ๋์ผํ ์๋ต ํฌ๋งท/์๊ฐ ๊ด๋ฆฌ
- โ ์ ์ง๋ณด์์ฑ: ๊ณตํต ๋ก์ง ์์ ์ ํ ๊ณณ๋ง ์์
- โ ๊ฐ๋ฐ ์์ฐ์ฑ: ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋ ์์ฑ ๋ถํ์
# application-docker.properties
spring.cloud.gateway.routes[0].id=auth-service
spring.cloud.gateway.routes[0].uri=lb://auth-service
eureka.client.service-url.defaultZone=http://eureka-server:8761/eureka/๊ฐ ECS Task๋ง๋ค ๋ ๋ฆฝ์ ์ธ Secret์ Secrets Manager๋ก ๊ด๋ฆฌํ์ฌ ๋ณด์์ฑ๊ณผ ๊ด๋ฆฌ ํจ์จ์ฑ์ ๋์์ต๋๋ค.
Secrets Manager ๊ตฌ์กฐ:
// eatcloud/auth-service/database
{
"username": "auth_service",
"password": "****",
"engine": "postgresql",
"host": "eatcloud-postgres.xxx.ap-northeast-2.rds.amazonaws.com",
"port": 5432,
"dbname": "auth_db"
}
// eatcloud/auth-service/redis
{
"host": "eatcloud-redis.xxx.cache.amazonaws.com",
"port": 6379
}
// eatcloud/auth-service/jwt
{
"secret": "****",
"expiration": 3600
}{
"family": "eatcloud-auth-service",
"containerDefinitions": [
{
"name": "auth-service",
"image": "xxx.dkr.ecr.ap-northeast-2.amazonaws.com/eatcloud/auth-service:latest",
"secrets": [
{
"name": "DB_USERNAME",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:xxx:secret:eatcloud/auth-service/database:username::"
},
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:xxx:secret:eatcloud/auth-service/database:password::"
},
{
"name": "REDIS_HOST",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:xxx:secret:eatcloud/auth-service/redis:host::"
}
]
}
]
}# application-aws.properties
# Eureka ์ฌ์ฉ ์ ํจ - ALB๊ฐ ๋ผ์ฐํ
๋ด๋น
eureka.client.enabled=false
# RDS ์ฐ๊ฒฐ - Secrets Manager์์ ์ฃผ์
spring.datasource.url=jdbc:postgresql://${DB_HOST}:5432/auth_db
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
# Redis - Secrets Manager์์ ์ฃผ์
spring.redis.host=${REDIS_HOST}
spring.redis.port=${REDIS_PORT:6379}
# JWT - Secrets Manager์์ ์ฃผ์
jwt.secret=${JWT_SECRET}
jwt.expiration=${JWT_EXPIRATION:3600}graph TB
subgraph "AWS Secrets Manager"
Secret1[auth-service secrets<br/>DB/Redis/JWT]
Secret2[customer-service secrets<br/>DB/Redis]
Secret3[order-service secrets<br/>DB/Redis/Kafka]
Secret4[payment-service secrets<br/>DB/Redis/PG]
end
subgraph "ECS Fargate Cluster"
Task1[Auth Service Task]
Task2[Customer Service Task]
Task3[Order Service Task]
Task4[Payment Service Task]
end
Secret1 -->|ํ๊ฒฝ๋ณ์ ์ฃผ์
| Task1
Secret2 -->|ํ๊ฒฝ๋ณ์ ์ฃผ์
| Task2
Secret3 -->|ํ๊ฒฝ๋ณ์ ์ฃผ์
| Task3
Secret4 -->|ํ๊ฒฝ๋ณ์ ์ฃผ์
| Task4
Task1 --> RDS1[(Auth DB)]
Task2 --> RDS2[(Customer DB)]
Task3 --> RDS3[(Order DB)]
Task4 --> RDS4[(Payment DB)]
Task1 --> Redis[(Redis Cluster)]
Task2 --> Redis
Task3 --> Kafka[MSK Kafka]
style Secret1 fill:#ffe1e1
style Secret2 fill:#ffe1e1
style Secret3 fill:#ffe1e1
style Secret4 fill:#ffe1e1
- โ ์ํธํ ์ ์ฅ: ๋ชจ๋ ๋ฏผ๊ฐ ์ ๋ณด๋ฅผ ์ํธํํ์ฌ ์ ์ฅ
- โ ์ ๊ทผ ์ ์ด: IAM ์ ์ฑ ์ผ๋ก ์๋น์ค๋ณ ์ ๊ทผ ๊ถํ ๋ถ๋ฆฌ
- โ ๊ฐ์ฌ ์ถ์ : CloudTrail๋ก Secret ์ ๊ทผ ๋ก๊ทธ ๊ธฐ๋ก
- โ ์ค์ ๊ด๋ฆฌ: ๋ชจ๋ ์๋น์ค์ Secret์ ํ ๊ณณ์์ ๊ด๋ฆฌ
- โ ๋ฒ์ ๊ด๋ฆฌ: Secret ๋ณ๊ฒฝ ์ด๋ ฅ ์ถ์ ๋ฐ ๋กค๋ฐฑ ๊ฐ๋ฅ
- โ ์๋ ๋กํ ์ด์ : ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋น๋ฐ๋ฒํธ ์๋ ๊ต์ฒด
- โ ์ฝ๋ ๋ถ๋ฆฌ: ๋ฏผ๊ฐ ์ ๋ณด๊ฐ ์ฝ๋์ ํ๋์ฝ๋ฉ๋์ง ์์
- โ ํ๊ฒฝ๋ณ ๊ด๋ฆฌ: dev/staging/prod ํ๊ฒฝ๋ณ Secret ๋ถ๋ฆฌ
- โ ์ฆ์ ๋ฐ์: Secret ๋ณ๊ฒฝ ์ Task ์ฌ์์๋ง์ผ๋ก ๋ฐ์

