feat: MySQL S3 백업 systemd 파이프라인 구성 - #71
Conversation
- systemd service 배포 워크플로우 작성 - validate 모드와 install 모드로 나누어 구현
📝 WalkthroughWalkthroughMySQL S3 백업 파이프라인을 추가한다. 공통 검증·업로드 유틸리티, dump/binlog 실행 스크립트, systemd 타이머, 원자적 설치 및 롤백, GitHub Actions 기반 AWS·SSM 원격 배포와 회귀 테스트를 구성한다. ChangesMySQL 백업 파이프라인
Estimated code review effort: 5 (Critical) | ~100 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant AWS
participant DBEC2
participant BackupInstaller
participant S3
GitHubActions->>AWS: Assume AWS role and start SSM port forwarding
GitHubActions->>DBEC2: Verify SSH host key fingerprint
GitHubActions->>DBEC2: Run validate-remote.sh or transfer installation bundle
DBEC2->>BackupInstaller: Execute install.sh in install mode
BackupInstaller->>S3: Validate bucket and upload backup artifacts
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Terraform Plan:
|
Terraform Plan:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b91ea935c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
scripts/mysql_backup/tests/run.sh (1)
147-152: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winbinlog 객체가 manifest보다 먼저 업로드되는지 검증하세요.
현재 개수만 확인하므로 순서가 뒤집혀도 통과합니다. manifest가 먼저 노출되면 불완전한 binlog를 복구 가능 상태로 오인할 수 있습니다.
검증 추가안
assert_equals "8" "$(wc -l <"$upload_log" | tr -d ' ')" "four closed binlogs and manifests must be uploaded" + mapfile -t uploaded_keys <"$upload_log" + for ((i = 0; i < ${`#uploaded_keys`[@]}; i += 2)); do + assert_equals \ + "${uploaded_keys[i]}.manifest.json" \ + "${uploaded_keys[i + 1]}" \ + "each binlog must be uploaded immediately before its manifest" + done🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/mysql_backup/tests/run.sh` around lines 147 - 152, Update the assertions in the test around upload_log to verify upload ordering, not only the total count: confirm each closed binlog entry is recorded before its corresponding manifest entry. Preserve the existing count, persisted state, and rotation assertions while using the upload_log contents to fail when a manifest is exposed before its binlog.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/mysql-backup-deploy.yml:
- Around line 31-45: Pin the actions used by the checkout and AWS credential
configuration steps to their full immutable commit SHAs instead of version tags.
Replace the Session Manager plugin’s latest download with a specific versioned
artifact, obtain its AWS-provided signature, verify the downloaded package with
the official signing key before installation, and only then run dpkg.
In `@scripts/mysql_backup/bin/mysql-backup-binlog`:
- Around line 94-115: Update the S3 recovery logic around uploaded_keys and
last_uploaded to validate manifest continuity before recording progress. Iterate
through the sorted binlog manifest numbers in order, require each adjacent
number to increase by exactly one, and set last_uploaded only to the final entry
in the contiguous sequence; stop before any gap so missing binlogs are not
treated as uploaded. Preserve the existing state reset and recovery message
behavior.
In `@scripts/mysql_backup/bin/mysql-backup-dump`:
- Around line 91-93: 백업 정리 순서를 조정하여 완료된 작업 포인터인 CURRENT_JOB_FILE을 staging 디렉터리인
JOB_DIR보다 먼저 삭제하세요. rm -f "$CURRENT_JOB_FILE"을 rm -rf "$JOB_DIR" 앞에 배치하고, 성공 상태
기록은 기존처럼 정리 작업 이후에 수행하세요.
In `@scripts/mysql_backup/install.sh`:
- Line 47: Separate the command substitution from the readonly declaration for
both SOURCE_DIR at scripts/mysql_backup/install.sh:4-4 and TRANSACTION_DIR at
scripts/mysql_backup/install.sh:47-47: assign each value first, then declare the
variable readonly on the following line so mktemp or other command failures
remain visible to set -e.
In `@scripts/mysql_backup/README.md`:
- Around line 10-17: Update the “배포 전 GitHub 설정” section in README.md to
document the required Repository Secret AWS_ROLE_ARN, including that it must
contain the AWS IAM role ARN used by the workflow. Keep the existing Repository
Variables guidance unchanged.
In `@scripts/mysql_backup/tests/run.sh`:
- Around line 282-295: Remove the previous manifest capture after copying it to
first_manifest and before the retry invocation of mysql-backup-dump. Update the
test flow around manifest_capture so the second run must recreate the capture,
ensuring cmp validates that the retry regenerated and reuploaded the manifest.
- Around line 4-5: run.sh의 PROJECT_DIR와 TEST_ROOT 선언을 readonly 선언과 값 할당으로 분리하세요.
먼저 readonly PROJECT_DIR/TEST_ROOT를 선언한 뒤 각 명령 치환을 별도 할당문으로 실행해 cd 또는 mktemp의 실패
상태가 호출자에게 전달되도록 하세요.
In `@scripts/mysql_backup/validate-remote.sh`:
- Around line 31-36: Update the pre-validation query in the
mysql-backup-validate workflow to evaluate the returned MySQL settings instead
of only checking whether the docker exec succeeds. Apply the same validation
conditions used by mysql-backup-validate for binlog enabled, binlog format,
server_id, sync_binlog, and innodb_flush_log_at_trx_commit, and fail validation
when any requirement is not met.
---
Nitpick comments:
In `@scripts/mysql_backup/tests/run.sh`:
- Around line 147-152: Update the assertions in the test around upload_log to
verify upload ordering, not only the total count: confirm each closed binlog
entry is recorded before its corresponding manifest entry. Preserve the existing
count, persisted state, and rotation assertions while using the upload_log
contents to fail when a manifest is exposed before its binlog.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 75a09b7a-f531-42b8-b099-88ff56d600f4
📒 Files selected for processing (14)
.github/workflows/mysql-backup-deploy.ymlmodules/app_stack/scripts/mysql_setup.sh.tftplscripts/mysql_backup/README.mdscripts/mysql_backup/bin/mysql-backup-binlogscripts/mysql_backup/bin/mysql-backup-dumpscripts/mysql_backup/bin/mysql-backup-validatescripts/mysql_backup/install.shscripts/mysql_backup/lib/backup-common.shscripts/mysql_backup/systemd/mysql-backup-binlog.servicescripts/mysql_backup/systemd/mysql-backup-binlog.timerscripts/mysql_backup/systemd/mysql-backup-dump.servicescripts/mysql_backup/systemd/mysql-backup-dump.timerscripts/mysql_backup/tests/run.shscripts/mysql_backup/validate-remote.sh
| @@ -0,0 +1,382 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
혹시 해당 스크립트는 로컬에서만 실행해야 하나요 ? 워크플로우 등에서 동작하도록 하면 좋을 거 같습니다 !
| @@ -0,0 +1,208 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
git에 기록된 파일 모드가 100644입니다. 워크플로우가 이 스크립트를 직접 실행하고(mysql-backup-deploy.yml:227) tar가 모드를 그대로 보존하므로, install 모드는 Permission denied로 즉시 실패합니다.
chmod +x scripts/mysql_backup/install.sh방어적으로 워크플로우 쪽도 bash "$install_dir/install.sh" ...로 호출하면 확실합니다.
(bin/mysql-backup-binlog도 혼자 644인데, 이쪽은 install -m 755로 정규화되니 동작에는 영향 없습니다. 일관성 차원에서만 참고해주세요.)
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
workflow_dispatch는 이 파일이 존재하는 아무 브랜치에서나 실행됩니다. 지금은 environment:도 브랜치 조건도 없어서, write 권한이 있으면 feature 브랜치에서 install.sh를 수정한 뒤 dispatch → prod DB에 root로 임의 코드 실행이 가능합니다. 리뷰·승인 경로를 전혀 거치지 않습니다.
jobs:
deploy:
runs-on: ubuntu-latest
environment: prod-db # required reviewers 지정
if: github.ref == 'refs/heads/main' || inputs.mode == 'validate'추가로 secrets.AWS_ROLE_ARN 역할의 trust policy sub 조건이 repo:...:ref:refs/heads/main으로 제한돼 있는지 확인 부탁드립니다. IAM은 코드로 확인이 안 돼서요. 제한돼 있지 않다면 위 게이트가 실질적인 유일한 방어선입니다.
| done | ||
| install -d -m 700 "$JOB_DIR" | ||
|
|
||
| if [[ ! -s "$DUMP_FILE" ]]; then |
There was a problem hiding this comment.
실패한 dump가 다음 날 성공으로 종료됩니다.
dump-current 포인터와 staging/dump-<job_id>가 실패 시 보존되고, 재실행하면 [[ ! -s "$DUMP_FILE" ]]가 false라 덤프를 다시 뜨지 않고 기존 파일을 업로드합니다. 그런데 재시도 주기가 타이머와 같은 하루입니다.
| 시각 | 동작 | 결과 |
|---|---|---|
| D일 03:00 | dump 성공 → manifest 업로드 실패 | exit 1, 포인터·staging 보존 |
| D+1일 03:00 | job_id를 그대로 재사용, 재덤프 없음 |
D일 데이터를 D일 키 프리픽스에 업로드 |
| " | exit 0 |
D+1일 백업은 존재하지 않음 |
created_at이 job_id에서 유도되니 타임스탬프 자체가 거짓말을 하지는 않지만, 종료 코드는 성공이고 해당 날짜 백업은 없습니다.
제안:
job_id나이 상한 (예: 6시간 초과면 포인터·staging 폐기하고 새 job 시작)- 하루를 기다리지 않는 당일 재시도 경로 —
Type=oneshot은Restart=를 못 쓰니OnFailure=로 짧은 간격 타이머를 킥하거나ExecStart내부 제한 재시도
|
|
||
| docker exec "$MYSQL_CONTAINER" sh -lc \ | ||
| 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysqldump -uroot --single-transaction --quick --source-data=2 --routines --events --triggers --hex-blob --set-gtid-purged=OFF --no-tablespaces "$1"' \ | ||
| sh "$MYSQL_DATABASE" | gzip -1 >"$partial_dump" |
There was a problem hiding this comment.
dump 시작 전 여유 공간 검사가 없습니다. 공간 검사(db_bytes * 2 + 256MB)는 bin/mysql-backup-validate:43-52에만 있고 설치 시점에 한 번 돕니다.
그런데 staging이 MySQL datadir와 같은 EBS입니다(backup-common.sh의 BACKUP_ROOT, MYSQL_DATA_DIR 모두 /mnt/mysql-data). 데이터가 커진 뒤 03:00에 이 gzip이 볼륨을 채우면 MySQL이 쓰기 실패로 죽습니다. 백업이 프로덕션을 죽이는 실패 모드라 머지 전에 막는 게 좋겠습니다.
- 이 스크립트 시작부에 validate와 동일한 검사를 넣고, 부족하면 덤프를 시작하지 않고 실패 처리
- 여유가 되면 staging 전용 볼륨 분리
(gzip 스트림을 aws s3 cp -로 바로 흘리는 방법도 있지만 재시도 멱등성이 깨지니 공간 검사 쪽을 권합니다.)
관련 이슈
다음 작업: 백업 실패 감지 후 내부 알림 API를 거쳐 Discord로 전달하는 경로를 구성합니다.
작업 내용
특이 사항
리뷰 반영 사항
1.2.835.0버전 경로에서 패키지와 서명을 함께 내려받고, 패키지 버전1.2.835.0-1, AWS 공개 키 fingerprint, GPG 서명이 모두 일치할 때만 설치하는 fail-closed 방식으로 변경했습니다.리뷰 요구사항 (선택)
Summary by CodeRabbit
새 기능
안정성 개선
문서