# CI/CD 파이프라인 — 평가 분석 > **기준 브랜치**: `origin/dev` (로컬 `dev`와 동일, 2026-05-25 기준) > **분석 범위**: `.github/workflows/`, `Dockerfile`, `docker-compose.yml`, `application.yaml`, `.gitignore`, `CONTRIBUTING.md` > **참고**: 본 문서는 CI/CD 대분류 3개 소분류에 대한 평가만 다룹니다. 다른 대분류는 별도 문서 참조. --- ## 0. 요약 (Executive Summary) | 소분류 | 만족도 | 한 줄 평 | |--------|--------|----------| | 적절한 배포 전략이 사용되었는가 | **부분 충족** | 환경 분리·이미지 태깅·헬스체크는 잘 잡혀 있으나, 무중단 배포·자동 롤백·다중 인스턴스가 없어 운영 안전성 측면에서 미흡 | | CI/CD 과정에서 민감 정보가 안전하게 처리되고 있는가 | **충족 (우수에 가까움)** | OIDC 기반 단기 자격증명 + SSM Parameter Store + `.gitignore` 정책까지 잘 되어 있음. Secret 스캔과 권한 최소화 검증이 남은 보강 포인트 | | 코드 커밋부터 빌드 및 배포까지의 과정이 명확하게 정의되고 자동화되어 있는가 | **충족** | 브랜치→환경 매핑, CI/Deploy 워크플로 분리, PR 템플릿, Jira 연동까지 자동화됨. 다만 CI에서 테스트가 빠져 있고 정적 분석·알림이 부족 | ### 가장 우선 처리하면 가치 큰 액션 (Top 5) 1. **CI에서 테스트를 실행하도록 변경** — 현재 `./gradlew build -x test`로 테스트가 빠져 있음. `dev/master push`와 PR 양쪽에서 테스트 실패 시 빌드를 막아야 함. (`ci.yml:36`) 2. **무중단 배포로 전환** — 현재 `docker stop && rm && up`은 다운타임 발생. compose `up -d --pull always --wait`만으로는 새 컨테이너가 healthy 되기 전 기존 컨테이너가 죽어 있음. 블루/그린, 또는 ALB + 다중 EC2 + ECS rolling 으로 전환 검토. 3. **자동 롤백 트리거 정의** — `deploy.yml`이 배포 실패 시 `exit 1`만 함. 이전 SHA의 이미지 + S3 compose로 자동 롤백하는 추가 step (또는 manual workflow_dispatch)을 도입. 4. **Secret/취약점 스캔 도입** — Gitleaks, Trivy(이미지), Dependabot/Dependency Review를 워크플로에 추가. 민감 정보 항목 만족도를 “우수”로 끌어올림. 5. **배포 결과 알림** — `discord.webhook` 인프라가 이미 있으니, deploy 성공/실패 결과를 동일 Discord 채널로 전송하는 step 추가. 운영 가시성 확보. --- ## 1. 적절한 배포 전략이 사용되었는가 ### 1.1 만족도: **부분 충족** ### 1.2 근거 (현재 상태) #### 잘 되어 있는 점 - **환경 분리가 명확함**: `dev` 브랜치 → `stg`, `master` 브랜치 → `prod`로 결정론적 매핑. `deploy.yml:22-32`에서 `DEPLOY_ENV`, `ROLE_ARN`, `SSM_PATH`를 분기. - **환경별 IAM Role 분리**: `STG_ROLE_ARN`, `PROD_ROLE_ARN`을 별도 Secret으로 분리해 stg에서 prod 리소스 접근 가능성 차단. - **불변 이미지 태깅(immutable tags)**: 이미지 태그가 `sha-${{ github.sha }}` (커밋 SHA 기반). 같은 태그가 덮어쓰기되지 않으므로 이전 버전 재배포(롤백)에 필요한 전제 충족. `ci.yml:78`. - **배포 산출물 버전 관리**: `docker-compose.yml`을 `s3://glit-images/deploy/${SHA}/docker-compose.yml`에 SHA별로 업로드. 이미지뿐 아니라 compose도 함께 버저닝되어 있어 “3시간 전 SHA로 되돌리기”가 이론적으로 가능. `ci.yml:84`, `deploy.yml:69`. - **헬스체크 기반 배포 검증**: docker-compose의 `healthcheck`가 `/actuator/health`를 폴링하고 (`docker-compose.yml:14-19`), 배포 step은 `docker compose up -d --pull always --wait`로 헬스체크 통과까지 대기 (`deploy.yml:72`). 그리고 SSM SendCommand 결과를 20회×15초로 폴링해 실패 감지. `deploy.yml:96-125`. - **Flyway 자동 마이그레이션**: `spring.flyway.enabled: true`, `baseline-on-migrate: false` (stg/prod), `out-of-order: true`로 스키마 변경이 앱 부팅과 동시에 적용됨. `application.yaml:34-39, 221-222, 254-256`. - **CI/Deploy 워크플로 분리**: 빌드(`ci.yml`)와 배포(`deploy.yml`)가 `workflow_run` trigger로 연결되어 빌드 실패 시 배포가 자동 차단됨. `deploy.yml:4-18`. #### 부족한 점 (감점 사유) - **무중단 배포가 아님 (다운타임 발생)**: `deploy.yml:70-72`에서 ```bash docker stop groute-server 2>/dev/null || true docker rm groute-server 2>/dev/null || true cd /home/ssm-user/glit && docker compose up -d --pull always --wait ``` → 기존 컨테이너를 **먼저 죽이고 나서** 새 컨테이너를 띄움. 새 컨테이너가 healthy 될 때까지 (`start_period: 30s`) 503 응답. 트래픽이 적은 시간대에는 무시할 수 있으나 “무중단”은 아님. - **단일 EC2 인스턴스 추정 (HA 부재)**: SSM SendCommand가 `Targets: [{ Key: "tag:Env", Values: [$env_val] }]`로 환경 태그가 같은 모든 인스턴스에 명령을 보내지만, 다중 인스턴스 + 로드밸런서를 동시에 다루지 않음. 단일 인스턴스 장애 = 서비스 중단. - **자동 롤백 메커니즘 부재**: `deploy.yml:111-119`에서 실패 시 출력만 찍고 `exit 1`. 이전 SHA로 자동 되돌아가지 않음. `사람이 직접 이전 SHA로 workflow_dispatch`해야 하는데, **수동 롤백 workflow조차 없음** (현재 `ci.yml`에 `workflow_dispatch` trigger가 없음). - **Flyway 롤백 비대칭**: `out-of-order: true` 설정이라 순서 깨진 마이그레이션도 적용됨. 그러나 Flyway는 자체적으로 down 마이그레이션을 지원하지 않음 (community 모듈만 존재). 앱 롤백 후 스키마가 안 맞으면 부팅 실패. **마이그레이션과 코드 롤백 짝을 어떻게 맞출지에 대한 정책 부재**. - **카나리/블루-그린/롤링 등 점진적 배포 전략 없음**: 100% 트래픽을 한 번에 새 버전으로 전환. - **승인 게이트(Manual Approval) 없음**: `master → prod`도 PR 머지 즉시 자동 배포. GitHub Environments의 required reviewer를 안 쓰고 있음. - **데이터베이스 마이그레이션과 배포 분리 정책 부재**: 백워드 호환되지 않는 스키마 변경을 어떻게 다룰지(expand-contract 패턴) 가이드가 없음. ### 1.3 남은 To-do - [ ] **무중단 배포로 전환** — 1순위. 선택지: - (A) 단일 EC2 유지, **Docker compose에 동일 서비스 2개 컨테이너 + nginx/traefik 리버스 프록시** → 블루/그린 흉내. 변경 비용 중간. - (B) **ECS Fargate 또는 EKS로 이전** → rolling deployment 기본 제공. 변경 비용 큼. - (C) **ALB + 다중 EC2 ASG** → ALB target group draining + rolling. 인프라 변경 큼. - 권장: 현 규모면 (A)로 시작, 트래픽 증가 시 (B). - [ ] **수동 롤백 workflow 추가** — `.github/workflows/rollback.yml`에 `workflow_dispatch` 입력으로 SHA를 받아 해당 이미지로 재배포. `IMAGE_TAG=sha-<input>`, `aws s3 cp s3://glit-images/deploy/<input>/docker-compose.yml ...`만 재실행. 30분이면 가능. - [ ] **자동 롤백 트리거(옵션)** — deploy step이 실패하면 마지막 성공 SHA(`HEAD~1`이 아닌 actual last green deploy)로 자동 재배포. 단, Flyway 마이그레이션 이미 적용된 경우 위험 → 마이그레이션 영향 여부를 사람이 판단해야 하므로 “알림만 보내고 사람이 결정”으로 시작 권장. - [ ] **prod 배포 승인 게이트** — GitHub Environments `prod` 생성 → required reviewer 2인 지정 → `deploy.yml`의 prod 분기에서 `environment: prod` 명시. PR 머지 즉시 prod 반영되는 위험 제거. - [ ] **Blue/Green 토글을 위한 docker-compose 변경** — 현재 컨테이너 이름이 `groute-server` 하드코딩. 색깔 구분된 두 서비스로 확장하고, nginx upstream 스위칭으로 무중단 실현. - [ ] **Flyway 마이그레이션 안전 가이드 문서화** — `CONTRIBUTING.md`에 “DROP/RENAME/NOT NULL ADD 같은 비호환 변경은 expand→migrate→contract 3단계로 분리” 정책 추가. - [ ] **배포 후 smoke test step 추가** — 헬스체크 통과만 보지 말고, 주요 API 1~2개(`GET /api/v1/users/me` 등)를 호출해 200 응답까지 확인. - [ ] **배포 메트릭/알림** — Discord webhook이 이미 stg/prod에 활성화되어 있으니 (`application.yaml:236-238, 277-279`), deploy.yml의 결과(성공/실패, SHA, 소요 시간)를 같은 채널로 발송. - [ ] **CDK/Terraform 등 IaC로 인프라 코드화** — 현재 EC2/SSM/ECR/Parameter Store가 콘솔에서 만들어졌을 가능성. 인프라가 코드화되어야 “배포 전략”이 진정한 의미에서 reproducible. --- ## 2. CI/CD 과정에서 민감 정보가 안전하게 처리되고 있는가 ### 2.1 만족도: **충족** (현재 운영 모범 사례에 가까움. 작은 보강만 더하면 우수) ### 2.2 근거 (현재 상태) #### 잘 되어 있는 점 - **AWS OIDC 단기 자격증명 사용**: 장기 IAM Access Key를 GitHub Secrets에 저장하는 안티패턴을 피함. - `ci.yml:13-15`, `deploy.yml:12-14`에 `permissions: id-token: write` 명시. - `aws-actions/configure-aws-credentials@v4`로 `role-to-assume` 기반 AssumeRoleWithWebIdentity. `ci.yml:60-65`, `deploy.yml:36-40`. - → 키 노출/탈취 리스크 거의 없음. - **환경별 IAM Role 분리**: `STG_ROLE_ARN`, `PROD_ROLE_ARN`을 다른 Secret으로 보관해 dev 워크플로가 prod 자원에 접근할 수 없게 분리. - **SSM Parameter Store + SecureString**: 실 운영 시크릿(`REDIS_PASSWORD`, OAuth 키, JWT_SECRET, Firebase JSON 등)을 `/groute/{env}/...` 경로에 보관. 배포 시 `aws ssm get-parameters-by-path --with-decryption`로 EC2에서만 평문화. `deploy.yml:64`. - **Secrets는 환경변수로만 노출**: GitHub Actions Secrets가 `${{ secrets.X }}` → step의 `env:` 블록으로 전달되며 `echo`되지 않음 (deploy.yml의 `ROLE_ARN`만 `GITHUB_ENV`로 쓰여지지만 `::add-mask::` 처리는 안 됨 — 이건 작은 보강 포인트). - **`.gitignore`의 보안 정책**: `.env`, `.env.*`, `application-local*`, `application-secret*` 모두 차단. `.gitignore:40-48`. → 실수로 .env 커밋할 위험 낮음. - **`application.yaml`은 placeholder만 보유**: 모든 민감 키가 `${...}` 형태. 평문 default가 있는 것은 `JWT_SECRET`(로컬용 자명한 placeholder)뿐이며 `local-dev-secret-key-must-be-changed-in-production-env` 같은 자기 설명형 문자열. `application.yaml:95`. - **fail-fast 정책**: stg/prod에서 `${USER_DEFAULT_PROFILE_IMAGE_URL}`, `${FIREBASE_CREDENTIALS_JSON}` 같은 필수 시크릿이 비어 있으면 부팅 실패하도록 default를 안 둠. `application.yaml:130, 158`. → 설정 오류 즉시 감지. - **REDIS_PASSWORD 검증**: `deploy.yml:68`이 SSM에서 가져온 값이 비어 있으면 `exit 1`. 옛 인시던트 회고 적용된 흔적. - **컨테이너 명령행에 비밀번호 노출 방지**: Redis healthcheck를 `REDISCLI_AUTH=$REDIS_PASSWORD redis-cli ping`로 환경변수로 전달 (`docker-compose.yml:32`) → `docker ps`/`ps ax`에 비밀번호 안 보임. - **/tmp/groute.env 정리**: `trap cleanup EXIT`로 SSM 명령 종료 시 환경 파일 삭제. `deploy.yml:59-60`. - **PR 빌드는 ECR push 안 함**: `if: github.event_name == 'push'` 가드로 PR fork 등에서 ECR 접근 가능성 차단. `ci.yml:52, 61, 68, 73, 83`. - **Jira/GitHub 토큰 분리**: Jira API는 `JIRA_API_TOKEN`(별도), GitHub 작업은 `secrets.GITHUB_TOKEN`(자동 발급)으로 분리. #### 부족한 점 (개선 여지) - **Secret/Credential 스캐닝 부재**: Gitleaks/TruffleHog 워크플로 없음. 누군가 실수로 키를 커밋해도 CI가 잡지 못함. GitHub native secret scanning(공개 레포 한정 자동, private은 GHAS 구독 필요)에 의존 중인 것으로 추정. - **의존성 취약점 스캔 부재**: Dependabot 설정 파일(`.github/dependabot.yml`) 없음. Trivy/Snyk/Grype 같은 이미지 스캔도 없음. - **`/tmp/groute.env` 평문 파일 잠시 존재**: trap으로 정리되지만 SSM 명령 실행 중에는 평문이 EC2 디스크에 존재. SSM RunShellScript 출력도 CloudWatch Logs로 갈 수 있어 비밀번호가 노출될 가능성. `set +x` 처리는 되어 있지만 fail-safe로 `chmod 600` 정도는 추가 권장. - **SSM 명령 결과 마스킹 부재**: 배포 실패 시 `CommandPlugins[0].Output`을 그대로 출력 (`deploy.yml:117`). 만약 앱이 시작 중에 비밀번호 일부를 로그에 찍는다면 GitHub Actions 로그로 새어 나갈 수 있음. - **Secret 회전(rotation) 정책 문서화 부재**: JWT_SECRET, OAuth client_secret 같은 키의 회전 주기/SOP가 어디에도 안 적혀 있음. - **`docker-compose.yml`의 `env_file: /tmp/groute.env`**: EC2 host의 절대 경로. compose가 띄울 때 평문 파일이 필요. K8s Secret처럼 메모리에만 존재하는 형태가 아님 — 운영 환경 등급에서는 작은 약점. - **OIDC Trust Policy 검증 미상**: GitHub Actions의 OIDC Provider에서 `repo:OWNER/REPO:ref:refs/heads/{dev,master}` 같은 조건이 IAM trust policy에 적용되어 있는지 확인 필요 (코드만으로는 알 수 없음). 잘못 설정되면 fork PR이 prod role을 가져갈 위험. - **워크플로 권한이 약간 넓음**: 일부 워크플로에서 `permissions:` 블록이 명시되어 있지만 (`ci.yml`, `deploy.yml`은 잘 되어 있음), `jira-issue-create.yml`의 `permissions: issues: write, contents: write`처럼 `contents: write`까지 부여된 경우는 적절성 재검토. ### 2.3 남은 To-do - [ ] **Gitleaks 워크플로 추가** — `.github/workflows/gitleaks.yml`로 push/PR 시 시크릿 패턴 검출. 작업 5분. - [ ] **Trivy 이미지 스캔 추가** — `ci.yml`의 빌드 후 step으로 `aquasecurity/trivy-action`. 취약점 high/critical 발견 시 fail. 작업 15분. - [ ] **Dependabot 활성화** — `.github/dependabot.yml` 생성 (gradle, docker, github-actions 세 ecosystem). 작업 5분. - [ ] **OIDC trust policy 점검** — AWS IAM에서 STG_ROLE/PROD_ROLE의 trust policy `sub` 조건이 `repo:2026-KUSITMS-GLIT/2026-KUSITMS-GLIT-BACK:ref:refs/heads/dev` / `master`로 핀 되어 있는지 확인. PR 분기까지 허용되면 안 됨. - [ ] **GitHub Environments 적용** — `stg`, `prod` 환경 객체 생성 → 환경 단위로 Secret 분리(현재 repo-level secret을 environment-level로 이동). PR 환경의 시크릿 노출 차단. - [ ] **`/tmp/groute.env` 권한 강화** — 생성 직후 `chmod 600 /tmp/groute.env` 추가. trap cleanup은 유지. - [ ] **SSM 출력 마스킹** — 배포 실패 출력에서 `REDIS_PASSWORD=...` 같은 라인을 grep으로 마스킹하고 출력. - [ ] **Secret 회전 SOP 문서화** — `CONTRIBUTING.md` 또는 별도 `docs/operations/secret-rotation.md`에 JWT_SECRET / OAuth client secret / AWS role / Jira token의 회전 절차와 주기 명시. - [ ] **Secret 사용 인벤토리 정리** — 어떤 시크릿이 어디서 쓰이는지 표로 정리해 한 곳에서 보이게. - [ ] **(선택) GitHub Advanced Security 활성화** — 조직 정책이 허락한다면 code scanning, secret scanning을 enable. --- ## 3. 코드 커밋부터 빌드 및 배포까지의 과정이 명확하게 정의되고 자동화되어 있는가 ### 3.1 만족도: **충족** ### 3.2 근거 (현재 상태) #### 잘 되어 있는 점 - **Git 흐름이 문서로 강제됨**: `CONTRIBUTING.md`에 GitHub Flow + dev/master 분리 정책, 브랜치 네이밍(`prefix/#issue-...`), 커밋 컨벤션(`prefix: 한국어`), PR 흐름이 일관되게 기술됨. - **워크플로 책임 분리가 명확**: - `ci.yml`: 빌드 + (PR이면) 이미지 빌드만 검증 + (push면) ECR push + S3에 compose 업로드. - `deploy.yml`: `workflow_run`으로 CI 성공 시에만 실행 → 환경 결정 → SSM SendCommand → 폴링. - `sync-dev-with-master.yml`: master 머지 시 dev를 master로 force-update해 두 브랜치 동기화. - `jira-issue-create.yml`, `jira-issue-close.yml`: GitHub Issue ↔ Jira 양방향 자동화. - **브랜치 → 환경 매핑이 결정론적**: dev push → stg, master push → prod. 사람이 환경을 고를 여지 없음 = 휴먼 에러 방지. `ci.yml:51-58`, `deploy.yml:23-32`. - **이슈 → 브랜치 자동 생성**: `jira-issue-create.yml`이 이슈 라벨(feat/fix/etc.)에 따라 Jira 이슈를 만들고, 영문 슬러그를 추출해 dev base의 브랜치를 자동 생성 + 이슈 제목에 `[GRT-XX]` prefix 자동 부여 + 코멘트로 git checkout 명령까지 자동 안내. - **PR 템플릿 강제**: `.github/pull_request_template.md`로 요약/변경 분류/테스트 방법/JaCoCo 커버리지 수치/롤백 리스크/관련 이슈를 강제로 채우게 함. - **빌드 캐시 도입**: gradle cache (`actions/setup-java@v4 with cache: 'gradle'`), docker buildx GHA cache (`cache-from: type=gha`, `cache-to: type=gha,mode=max`). CI 속도 개선. - **PR에서 Docker 빌드까지 검증**: 단순 `gradlew build`만 하지 않고, PR에서 Docker 이미지 빌드 자체가 성공하는지까지 검증 (`ci.yml:41-49`). 즉 “merge 후 도커 빌드 실패”를 PR 단계에서 차단. - **배포 상태 폴링**: `deploy.yml:96-125`가 20회×15초로 SSM 명령 상태를 폴링하고, Success/Failed/Cancelled/TimedOut을 명확히 분기. - **`master ↔ dev` 자동 동기화**: hotfix 시 dev가 master보다 뒤처지는 사고를 자동으로 막음 (`sync-dev-with-master.yml`). - **빌드 결과 코드포맷팅 게이트**: spotless가 build.gradle에 설정되어 있고 (`build.gradle:116-129`), 최근 커밋(`e7ecc1e style: spotless 코드 포맷 적용`)도 적용되어 있어 포맷 일관성 유지. - **테스트 커버리지 게이트(로컬)**: `jacocoTestCoverageVerification` 60% 게이트가 `check` task에 묶여 있음 (`build.gradle:112-114`). 로컬 `./gradlew check`에서 강제됨. #### 부족한 점 (감점 사유) - **CI에서 테스트가 실행되지 않음 (큰 문제)**: `ci.yml:36`이 `./gradlew build -x test`로 테스트를 명시적으로 스킵. 즉 PR/dev/master 어디서도 자동 테스트가 안 돌아감. `CONTRIBUTING.md`는 “로컬에서 `./gradlew check` 권장”이라 적혀 있지만 사람에 의존. JaCoCo 60% 게이트도 CI에서 검증 안 됨. - **정적 분석/품질 게이트 부재**: spotless가 빌드 시 자동 apply 되어 있지 않고 (`spotlessCheck` step 없음), 위반 시 CI fail 안 됨. CodeQL/SonarCloud 같은 정적 분석 없음. - **배포 후 검증(smoke/synthetic)이 헬스체크 단 한 줄**: `/actuator/health`만 200이면 배포 성공으로 간주. 실제 비즈니스 API가 동작하는지는 검증 안 함. - **배포 알림 부재**: Discord webhook 인프라는 있는데 deploy.yml이 활용 안 함. 배포 시작/성공/실패가 GitHub Actions 페이지에서만 보임. - **롤백 SOP 문서화 부재**: `CONTRIBUTING.md`에 “문제 발생 시 reopen하거나 fix 이슈 생성”까지만 적혀 있고, 운영 롤백(이전 SHA로 되돌리기) 절차가 없음. - **수동 트리거(workflow_dispatch) 없음**: 긴급 재배포·롤백 시 임시 SHA로 재배포할 방법이 없음. 잘못된 머지를 revert하고 다시 push해야 함 → 시간 손실. - **`workflow_run` 패턴의 사이드 이펙트**: deploy.yml이 `workflow_run`으로 트리거되므로, master 직접 push(=금지지만 가능)·sync-dev-with-master force-update가 dev에서 CI를 트리거하면 dev 자동 배포가 한 번 더 도는지 확인 필요. force-update는 push 이벤트를 발생시키지 않을 수도 있어 동작 검증 필요. - **마이그레이션 검증 단계 부재**: Flyway 마이그레이션이 staging에서 적용된 후 prod에 적용되지만, 마이그레이션 자체를 lint/dry-run 하는 단계가 없음. `seed_dummy_staging_*.sql` 파일이 추적되지 않은 상태로 워킹 트리에 남아 있는 것도 이슈. - **빌드 산출물(JAR) 보존 부재**: 이미지에는 빌드되지만 GitHub Actions artifact로 JAR을 따로 보존하지 않아, 빌드 산출물 직접 재배포가 어려움 (이미지 push만 의존). - **테스트 결과 리포트 업로드 부재**: 만약 CI에서 테스트를 켜더라도 JUnit XML / JaCoCo HTML을 artifact로 안 올리고 있어 PR 리뷰어가 결과를 볼 수 없음. ### 3.3 남은 To-do - [ ] **CI에서 테스트 켜기 (최우선)** — `ci.yml:36`을 `./gradlew check` 또는 `./gradlew build`로 변경. 단, 현재 빌드가 테스트 포함 시 통과하는지 먼저 로컬 확인 필요. PR/dev/master 모두 적용. - [ ] **테스트/커버리지 리포트 PR에 노출** - `actions/upload-artifact`로 `build/reports/jacoco/**`, `build/test-results/**` 업로드. - `dorny/test-reporter` 또는 `MikePenz/action-junit-report`로 PR 코멘트에 결과 표시. - `madrapps/jacoco-report`로 커버리지 % PR 코멘트. - [ ] **spotlessCheck를 CI에 추가** — `./gradlew spotlessCheck`를 빌드 step에 추가. 포맷 안 맞으면 CI fail. - [ ] **수동 트리거(workflow_dispatch) 추가** — `ci.yml`/`deploy.yml`에 `workflow_dispatch` trigger 추가하고 입력으로 SHA를 받아 임의 시점 재배포 가능하게. - [ ] **롤백 SOP 문서화** — `CONTRIBUTING.md`에 “7. 운영 / 롤백” 섹션 추가: - 이전 성공 SHA 찾는 법 (Actions deployment history) - workflow_dispatch로 재배포 절차 - Flyway 호환성 체크 항목 (down 마이그레이션이 없으므로 expand-contract 위반 시 롤백 불가) - [ ] **배포 알림 step 추가** — `deploy.yml`의 마지막에 Discord webhook 호출 step (성공/실패 + SHA + actor + env). - [ ] **smoke test step 추가** — 배포 후 curl로 `/actuator/health`뿐 아니라 1~2개 핵심 GET 엔드포인트(인증 불필요한 것)를 검증. - [ ] **Flyway 마이그레이션 lint** — `flyway validate` 또는 `flyway info -dryRun`을 CI에서 staging DB에 대해 한 번 돌리는 단계. 또는 PR에서 `db/migration/*.sql` 변경 시 SQL 린트(sqlfluff) 수행. - [ ] **`workflow_run` 트리거 행동 검증** — sync-dev-with-master.yml의 force-update가 CI를 다시 트리거하는지(이중 배포 위험) 테스트 → 필요시 path 필터/`paths-ignore` 추가. - [ ] **`seed_dummy_staging_*.sql` 정리** — 워킹 트리에 untracked로 있는 상태. .gitignore에 넣거나 `db/seeds/`로 옮겨 관리 정책을 분명히. - [ ] **빌드 산출물 artifact 보존** — `actions/upload-artifact`로 `build/libs/*.jar` 업로드, 보존 주기 7일. - [ ] **PR에서 Docker 이미지 빌드 결과 사이즈/취약점 코멘트** — Trivy 결과 + 이미지 사이즈를 PR 코멘트로 알림. - [ ] **README에 “배포 흐름” 다이어그램 추가** — README가 한 줄짜리(`README.md` 7바이트). 적어도 “PR → CI → ECR → SSM → EC2” 흐름과 환경별 매핑이 있어야 신규 인원이 이해 가능. - [ ] **CI 시간 측정/추적** — Actions의 builds 페이지에서 평균 소요 시간 모니터링. 현재 캐시 도입 후 빠르긴 한데, 지표화 안 되어 있음. --- ## 4. 평가 매트릭스 (실행 우선순위) > ROI (가치/비용) 관점에서 우선순위를 매긴 액션 매트릭스. | 우선 | 액션 | 영향 영역 | 예상 작업량 | 효과 | |------|------|-----------|-------------|------| | P0 | CI에서 `./gradlew check` 실행 | (3) 자동화 | 10분 | 회귀 방지, 커버리지 게이트 CI로 이동 | | P0 | Gitleaks 워크플로 추가 | (2) 민감정보 | 10분 | 시크릿 누출 방지 | | P0 | rollback workflow_dispatch 추가 | (1) 배포전략 | 30분 | 운영 안정성 ↑↑ | | P1 | Trivy 이미지 스캔 | (2) 민감정보 | 30분 | 컨테이너 취약점 가시화 | | P1 | Dependabot 설정 | (2) 민감정보 | 5분 | 의존성 패치 자동화 | | P1 | 배포 결과 Discord 알림 | (3) 자동화 | 20분 | 가시성 ↑ | | P1 | spotlessCheck CI step | (3) 자동화 | 5분 | 포맷 일관성 강제 | | P1 | GitHub Environments + prod 승인 게이트 | (1)(2) | 30분 | 실수 prod 배포 방지 | | P2 | 무중단 배포 (compose 2-color) | (1) 배포전략 | 1~2일 | 다운타임 제거 | | P2 | Flyway expand-contract 가이드 문서화 | (1) 배포전략 | 2시간 | 마이그레이션 사고 예방 | | P2 | smoke test step | (1)(3) | 1시간 | 헬스체크 외 회귀 검출 | | P2 | OIDC trust policy 점검 | (2) 민감정보 | 30분 (확인만) | 권한 경계 검증 | | P3 | IaC(CDK/Terraform)화 | (1) 배포전략 | 1~2주 | 재현 가능한 인프라 | | P3 | ECS Fargate로 이전 | (1) 배포전략 | 1~2주 | rolling deployment 기본 | | P3 | 빌드 산출물 artifact 보존 | (3) 자동화 | 10분 | 산출물 추적성 | --- ## 5. 평가 근거 파일 목록 > 평가에 직접 참조한 파일/라인. 추후 재검증 시 참조. - `.github/workflows/ci.yml` (1–85): 빌드/푸시 파이프라인. - `.github/workflows/deploy.yml` (1–126): SSM SendCommand 기반 배포. - `.github/workflows/sync-dev-with-master.yml` (1–25): master → dev force sync. - `.github/workflows/jira-issue-create.yml`, `jira-issue-close.yml`: 이슈 자동화. - `.github/pull_request_template.md`: PR 템플릿. - `Dockerfile` (1–6): 단순 JDK 21 base, no multi-stage. - `docker-compose.yml` (1–35): app + redis, healthcheck 포함. - `src/main/resources/application.yaml` (1–285): 환경 프로파일 분기. - `.gitignore` (40–48): 시크릿 차단 정책. - `build.gradle` (55–129): jacoco/spotless 설정. - `CONTRIBUTING.md` (전체): git/PR/마이그레이션 컨벤션. --- ## 6. 자가 검증 체크 (이 평가가 신뢰할 만한가) - [x] `origin/dev`와 로컬 `dev`가 동일함을 확인 (`git rev-list --left-right --count` 결과 `0 0`). - [x] `.github/workflows` 내 5개 파일을 모두 1라인부터 끝까지 읽음. - [x] `Dockerfile`, `docker-compose.yml`, `application.yaml`, `build.gradle`, `.gitignore` 1라인부터 끝까지 읽음. - [x] CI/CD 관련 최근 커밋(`git log --oneline origin/dev -- .github/workflows/`)을 확인해 의도 파악. - [x] `secrets.*` 사용 위치를 grep으로 전수 확인. - [ ] **미확인**: AWS 콘솔의 IAM Role trust policy 실제 내용 (코드만으로는 알 수 없음). - [ ] **미확인**: SSM Parameter Store의 실제 키 인벤토리 및 SecureString 여부. - [ ] **미확인**: EC2 인스턴스 개수 (단일/다중) — 단일로 추정했으나 실제 확인 필요. - [ ] **미확인**: 브랜치 보호 규칙 (required reviewers, required checks, force-push 차단 등). > 위 “미확인” 항목은 코드 외부 시스템 확인이 필요하므로, 평가 정확도를 높이려면 인프라/조직 설정도 함께 점검 권장.