Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .claude/hooks/log-bash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
# PostToolUse 훅: Bash 툴 실행 결과를 session-log.md에 기록
# stdin으로 JSON을 받아 command와 output을 추출한다

INPUT=$(cat)

COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
OUTPUT=$(echo "$INPUT" | jq -r '.tool_response | if type == "string" then . else (.stdout // "") end' 2>/dev/null || echo "")
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# 출력이 없거나 짧은 명령어는 기록하지 않음
if [ -z "$OUTPUT" ] || [ "$OUTPUT" = "null" ] || [ ${#OUTPUT} -lt 10 ]; then
exit 0
fi

# git, ls 같은 탐색 명령어는 스킵
case "$COMMAND" in
git\ log*|git\ status*|git\ diff*|ls*|pwd|echo*|cat*)
exit 0
;;
esac

LOG_FILE=".claude/session-log.md"

# 파일이 없으면 헤더 생성
if [ ! -f "$LOG_FILE" ]; then
echo "# Session Log" > "$LOG_FILE"
echo "" >> "$LOG_FILE"
fi

{
echo "### $TIMESTAMP"
echo '```bash'
echo "$ $COMMAND"
echo "$OUTPUT"
echo '```'
echo ""
} >> "$LOG_FILE"
18 changes: 18 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"permissions": {
"defaultMode": "bypassPermissions"
},
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/log-bash.sh"
}
]
}
]
}
}
65 changes: 65 additions & 0 deletions .claude/skills/doc-writer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: doc-writer
description: 최근 구현 작업을 분석해 docs/ 에 트러블슈팅 기록을 작성한다. 에러 메시지, 원인, 해결책, 재현 명령어를 포함한다.
argument-hint: [docs-file-path]
allowed-tools: Read Glob Grep Bash Edit Write
---

## 역할

최근 작업에서 발생한 에러와 해결 과정을 분석해 `docs/` 디렉토리에 트러블슈팅 문서를 작성한다.

## 작업 순서

### 1. 세션 로그 확인

`.claude/session-log.md`를 읽어 이번 세션에서 실행된 명령어와 출력을 파악한다.
에러가 포함된 출력을 중심으로 문서화 대상을 추출한다.

### 2. 문서 파일 결정

`$ARGUMENTS`가 주어지면 해당 경로에 작성한다.
비어있으면 `docs/` 디렉토리의 기존 파일 목록을 확인하고 가장 관련성 높은 파일을 선택한다.
관련 파일이 없으면 새 파일을 생성한다.

### 3. 현재 대화 컨텍스트에서 에러 수집

대화에서 발생했던 에러를 정리한다:
- 실제 에러 메시지 (터미널 출력 그대로)
- 에러가 발생한 상황
- 시도했던 해결 방법들
- 최종 해결책

### 4. 문서 작성

아래 형식으로 각 문제를 작성한다:

```markdown
## 문제 N: 제목 — 한 줄 요약

### 증상

\```
실제 에러 메시지를 그대로 붙여넣는다.
생략하거나 요약하지 않는다.
\```

### 원인

왜 이 에러가 발생했는지 설명한다.

### 해결

실제로 동작한 명령어나 코드 변경사항을 작성한다.

\```bash
# 실행한 명령어
\```
```

## 주의사항

- 에러 메시지는 절대 요약하지 않는다. 터미널 출력 그대로 코드 블록에 넣는다.
- 해결되지 않은 문제는 "미해결" 또는 "근본 해결 방향"으로 구분한다.
- 기존 문서가 있으면 덮어쓰지 말고 새 섹션을 추가한다.
- 재현 가능한 최소 명령어를 항상 포함한다.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
!.env.example
*.log
.plan/
.claude/session-log.md
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ curl http://localhost:3000/health
curl http://localhost:3000/ready

# 아이템 생성
curl -X POST http://localhost:3000/items \
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d '{"name": "test", "description": "hello"}'

# 목록 조회
curl http://localhost:3000/items
curl http://localhost:3000/api/items
```

### 4. 종료
Expand Down Expand Up @@ -148,11 +148,11 @@ TEST_DATABASE_URL=postgresql://devopsim:devopsim@localhost:5432/devopsim \
|--------|------|------|
| GET | /health | liveness — 프로세스 생존 확인 |
| GET | /ready | readiness — DB 연결 상태 확인 |
| POST | /items | 아이템 생성 |
| GET | /items | 목록 조회 |
| GET | /items/:id | 상세 조회 |
| PUT | /items/:id | 수정 |
| DELETE | /items/:id | 삭제 |
| POST | /api/items | 아이템 생성 |
| GET | /api/items | 목록 조회 |
| GET | /api/items/:id | 상세 조회 |
| PUT | /api/items/:id | 수정 |
| DELETE | /api/items/:id | 삭제 |

---
## 주차별 진행
Expand Down
61 changes: 61 additions & 0 deletions infra/k8s/base/api/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
labels:
app.kubernetes.io/name: api
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: devopsim
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: api
template:
metadata:
labels:
app.kubernetes.io/name: api
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: devopsim
spec:
initContainers:
- name: wait-for-db
image: postgres:16-alpine
command:
[
"sh",
"-c",
"until pg_isready -h db -p 5432; do echo 'waiting...'; sleep 2; done",
]

containers:
- name: api
image: devopsim-api:latest

Check warning on line 33 in infra/k8s/base/api/deployment.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a specific version tag for the image instead of "latest".

See more on https://sonarcloud.io/project/issues?id=f-lab-edu_devopsim&issues=AZ17O98UXISY38E69vNW&open=AZ17O98UXISY38E69vNW&pullRequest=2
imagePullPolicy: IfNotPresent
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-secret
key: database-url
ports:
- containerPort: 3000
protocol: TCP

livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5

readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 3
25 changes: 25 additions & 0 deletions infra/k8s/base/api/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress # nginx를 쓴다
metadata:
name: api
labels:
app.kubernetes.io/name: api
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: devopsim
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
#
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 80
# L4
# kind: Ingress vs Service: lb
10 changes: 10 additions & 0 deletions infra/k8s/base/api/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 로컬 적용 방법:
# kubectl create secret generic api-secret \
# --from-literal=database-url="postgresql://devopsim:devopsim@db:5432/devopsim"

Check failure on line 3 in infra/k8s/base/api/secret.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure this PostgreSQL password gets changed and removed from the code.

See more on https://sonarcloud.io/project/issues?id=f-lab-edu_devopsim&issues=AZ17O98LXISY38E69vNV&open=AZ17O98LXISY38E69vNV&pullRequest=2
apiVersion: v1
kind: Secret
metadata:
name: api-secret
type: Opaque
stringData:
database-url: ""
18 changes: 18 additions & 0 deletions infra/k8s/base/api/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: api
labels:
app.kubernetes.io/name: api
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: devopsim
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: api
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
# internal loadbalancer
14 changes: 14 additions & 0 deletions infra/k8s/base/db/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 로컬 적용 방법:
# kubectl create secret generic postgres-secret \
# --from-literal=postgres-db=devopsim \
# --from-literal=postgres-user=devopsim \
# --from-literal=postgres-password=devopsim
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
postgres-db: ""
postgres-user: ""
postgres-password: ""
17 changes: 17 additions & 0 deletions infra/k8s/base/db/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: db
labels:
app.kubernetes.io/name: db
app.kubernetes.io/component: database
app.kubernetes.io/part-of: devopsim
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: db
ports:
- name: postgres
port: 5432
targetPort: 5432
protocol: TCP
70 changes: 70 additions & 0 deletions infra/k8s/base/db/statefulset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
labels:
app.kubernetes.io/name: db
app.kubernetes.io/component: database
app.kubernetes.io/part-of: devopsim
spec:
serviceName: db
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: db
template:
metadata:
labels:
app.kubernetes.io/name: db
app.kubernetes.io/component: database
app.kubernetes.io/part-of: devopsim
spec:
containers:
- name: db
image: postgres:16-alpine
ports:
- containerPort: 5432
protocol: TCP
env:
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-db
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password

livenessProbe:
exec:
command: ["pg_isready", "-U", "devopsim"]
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 5

readinessProbe:
exec:
command: ["pg_isready", "-U", "devopsim"]
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 5

volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data

volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
9 changes: 9 additions & 0 deletions infra/k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- api/deployment.yaml
- api/service.yaml
- api/ingress.yaml
- db/statefulset.yaml
- db/service.yaml
Loading
Loading