-
Notifications
You must be signed in to change notification settings - Fork 0
prod DB 를 RDS 에서 자체 관리 EC2 MySQL 로 이관 #899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
bccb77a
infra: prod DB 를 RDS 에서 자체 관리 EC2 MySQL 로 이관
m-a-king 0ae6feb
infra: DB 박스의 백업 실패 감지와 보안그룹·삭제 방어를 보강한다
m-a-king a57d468
infra: MySQL root 자격증명을 앱 계정과 분리하고 원격 root 접속을 닫는다
m-a-king 1b03d43
infra: DB 인스턴스 종료 보호를 AWS 층까지 확장한다
m-a-king 95f3bcb
infra: DB 박스에 관측 수집기를 붙이고 cron 갱신의 멱등성을 고친다
m-a-king 4cfeb2e
infra: RDS 를 폐기하고 관련 정의를 걷어낸다
m-a-king 4ff23c4
infra: 프로비저닝이 자격증명 불일치와 관측 누락을 조용히 넘기지 않게 한다
m-a-king File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| #!/usr/bin/env bash | ||
| # DB 박스 논리 백업 — mysqldump 를 gzip 해 S3 에 올린다 (#898). | ||
| # | ||
| # cron 이 하루 한 번 호출한다(설치는 provision-db.sh). RDS 를 걷어내면서 사라진 관리형 백업을 | ||
| # 대신하는 유일한 복구 경로이므로, 실패를 조용히 넘기지 않고 로그에 남기고 비정상 종료한다. | ||
| # | ||
| # --single-transaction 이 핵심이다. InnoDB 의 일관된 스냅샷을 트랜잭션으로 떠서 테이블 락을 | ||
| # 걸지 않으므로, 백업 중에도 서비스는 그대로 읽고 쓴다. | ||
| set -euo pipefail | ||
|
|
||
| AWSCLI_IMAGE="public.ecr.aws/aws-cli/aws-cli:2.35.21" | ||
| CONTAINER="piki-prod-mysql" | ||
| REGION="ap-northeast-2" | ||
| SSM_PREFIX="/piki-core/prod" | ||
| WORK_DIR="/var/tmp/piki-db-backup" | ||
|
|
||
| log() { echo "[db-backup] $(date -u +%Y-%m-%dT%H:%M:%SZ) $*"; } | ||
|
|
||
| ssm_param() { | ||
| docker run --rm --network host "$AWSCLI_IMAGE" ssm get-parameter \ | ||
| --name "${SSM_PREFIX}/$1" --with-decryption \ | ||
| --region "$REGION" --query Parameter.Value --output text | ||
| } | ||
|
|
||
| DB_NAME="$(ssm_param db-name)" || { log "SSM db-name 조회 실패"; exit 1; } | ||
| # 덤프는 root 로 뜬다(전 스키마 접근). root 비밀번호는 앱 계정(db-password)과 분리돼 있으므로 | ||
| # 여기서 db-root-password 를 읽는다 — 앱 자격증명이 새도 백업 경로의 권한은 함께 넘어가지 않는다. | ||
| DB_ROOT_PASSWORD="$(ssm_param db-root-password)" || { log "SSM db-root-password 조회 실패"; exit 1; } | ||
| BUCKET="$(ssm_param db-backup-bucket)" || { log "SSM db-backup-bucket 조회 실패"; exit 1; } | ||
|
|
||
| TS="$(date -u +%Y%m%dT%H%M%SZ)" | ||
| FILE="${DB_NAME}-${TS}.sql.gz" | ||
| mkdir -p "$WORK_DIR" | ||
|
|
||
| # 덤프 → gzip. root 로 뜬다(스키마 전체와 routine·trigger 까지 읽어야 한다). | ||
| # --databases 를 쓰면 CREATE DATABASE / USE 문이 함께 담겨 빈 서버에 그대로 복원된다. | ||
| # --routines --triggers --events: 스키마 외 객체까지 포함해 "이 파일 하나면 복구된다"를 지킨다. | ||
| # | ||
| # 파이프 중간(mysqldump)의 실패를 놓치지 않도록 set -o pipefail 이 위에서 켜져 있다 — | ||
| # 이게 없으면 덤프가 깨져도 gzip 성공 코드만 보고 손상된 파일을 업로드한다. | ||
| log "덤프 시작: ${DB_NAME}" | ||
| if ! docker exec -e MYSQL_PWD="$DB_ROOT_PASSWORD" "$CONTAINER" \ | ||
| mysqldump -u root --single-transaction --routines --triggers --events \ | ||
| --databases "$DB_NAME" 2>"${WORK_DIR}/dump.err" | gzip > "${WORK_DIR}/${FILE}"; then | ||
| log "덤프 실패: $(tail -3 "${WORK_DIR}/dump.err")" | ||
| rm -f "${WORK_DIR}/${FILE}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| SIZE="$(stat -c %s "${WORK_DIR}/${FILE}")" | ||
| # 빈 파일·헤더만 있는 산출물이 "성공"으로 올라가 복구 시점에야 발각되는 걸 막는다. | ||
| # 정상 덤프는 gzip 후에도 최소 수 KB 다. | ||
| if [ "$SIZE" -lt 1024 ]; then | ||
| log "덤프 산출물이 비정상적으로 작다(${SIZE} bytes) — 업로드 중단" | ||
| rm -f "${WORK_DIR}/${FILE}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| log "업로드: s3://${BUCKET}/${FILE} (${SIZE} bytes)" | ||
| if ! docker run --rm --network host -v "${WORK_DIR}:/backup" "$AWSCLI_IMAGE" \ | ||
| s3 cp "/backup/${FILE}" "s3://${BUCKET}/${FILE}" --region "$REGION" > /dev/null; then | ||
| log "S3 업로드 실패 — 로컬 파일은 남겨 둔다(${WORK_DIR}/${FILE})" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # 업로드가 끝난 로컬 사본은 지운다. 루트 볼륨이 10GB 라 쌓이면 곧 찬다. | ||
| # (실패 시엔 위에서 남겨 두고 종료해 수동 회수 여지를 준다.) | ||
| rm -f "${WORK_DIR}/${FILE}" "${WORK_DIR}/dump.err" | ||
| log "완료" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| #!/usr/bin/env bash | ||
| # DB 박스 런타임 프로비저닝 — 멱등(idempotent). (#898) | ||
| # | ||
| # terraform 의 user_data 는 docker·swap 까지만 깔고 끝난다. MySQL 기동과 백업 cron 설치는 | ||
| # 여기가 맡는다 — user_data 는 첫 부팅에만 실행돼 스크립트를 고쳐도 반영되지 않고, 반영하려면 | ||
| # 인스턴스 교체가 필요한데 이 박스에서 교체는 곧 데이터 손실이기 때문이다. | ||
| # | ||
| # 실행: 이 스크립트와 db-backup.sh, 그리고 공용 alloy 블록을 함께 박스로 올린 뒤 돌린다. | ||
| # scp -i ~/.ssh/piki-ec2-connect infra/scripts/{provision-db.sh,db-backup.sh} ubuntu@<DB_PUBLIC_IP>:/tmp/ | ||
| # scp -i ~/.ssh/piki-ec2-connect -r <infra-repo>/blocks/alloy ubuntu@<DB_PUBLIC_IP>:/tmp/ | ||
| # ssh -i ~/.ssh/piki-ec2-connect ubuntu@<DB_PUBLIC_IP> 'bash /tmp/provision-db.sh' | ||
| # (키페어가 없는 박스라 접속 전에 EC2 Instance Connect 로 공개키를 밀어 넣어야 한다 — README 참고.) | ||
| # | ||
| # alloy 블록을 core 에 복사해 두지 않고 매번 infra 에서 올리는 이유는 배포 워크플로와 같다 — | ||
| # 그 블록의 SSOT 는 TeamPiKi/infra 이고, 사본을 두면 조용히 갈라진다. 안 올리면 4절이 건너뛴다. | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
|
||
| # --skip-alloy: 관측 수집기 설치를 의도적으로 생략한다(긴급 복구 등). 기본은 생략하지 않으며, | ||
| # 블록이 없으면 실패한다 — 관측 공백이 조용히 생기는 걸 막는 게 기본값이어야 한다. | ||
| SKIP_ALLOY=false | ||
| for arg in "$@"; do | ||
| case "$arg" in | ||
| --skip-alloy) SKIP_ALLOY=true ;; | ||
| *) echo "알 수 없는 인자: $arg" >&2; exit 2 ;; | ||
| esac | ||
| done | ||
|
|
||
| AWSCLI_IMAGE="public.ecr.aws/aws-cli/aws-cli:2.35.21" | ||
| MYSQL_IMAGE="mysql:8.4" | ||
| CONTAINER="piki-prod-mysql" | ||
| VOLUME="piki-prod-mysql-data" | ||
| REGION="ap-northeast-2" | ||
| SSM_PREFIX="/piki-core/prod" | ||
|
|
||
| # 컨테이너 메모리 캡. t4g.micro(실가용 약 906MB)에서 OS·docker 몫을 남기려면 상한이 필요하다. | ||
| # 384m 은 dev 박스 같은 구성의 실사용(111MB)의 3배 이상이고, InnoDB 버퍼풀 기본값(128MB)에 | ||
| # 데이터 전량(2.1MB)이 올라가는 상태를 여유 있게 덮는다. swap 은 캡의 2배로 둬 순간 초과를 흡수한다. | ||
| MEM_LIMIT="384m" | ||
| MEM_SWAP="768m" | ||
|
|
||
| ssm_param() { | ||
| docker run --rm --network host "$AWSCLI_IMAGE" ssm get-parameter \ | ||
| --name "${SSM_PREFIX}/$1" --with-decryption \ | ||
| --region "$REGION" --query Parameter.Value --output text | ||
| } | ||
|
|
||
| # ── 1) MySQL ──────────────────────────────────────────────────────────────── | ||
| # 자격증명은 컨테이너를 "최초 생성"할 때만 쓰인다. 이미 있으면 값이 안 쓰이지만 조회는 매번 한다 — | ||
| # 재생성 때만 도는 경로로 두면 조용히 썩은 채 가장 필요한 순간에 터진다(provision-runtime.sh 와 같은 규율). | ||
| DB_NAME="$(ssm_param db-name)" || { echo "[mysql] SSM db-name 조회 실패 — IAM(piki-prod-db-policy)·파라미터 존재 확인"; exit 1; } | ||
| DB_USERNAME="$(ssm_param db-username)" || { echo "[mysql] SSM db-username 조회 실패"; exit 1; } | ||
| DB_PASSWORD="$(ssm_param db-password)" || { echo "[mysql] SSM db-password 조회 실패"; exit 1; } | ||
| # root 는 앱 계정과 다른 비밀번호를 쓴다. 같은 값을 공유하면 앱 자격증명이 새는 순간 DB 전체 | ||
| # 권한까지 함께 넘어간다 — 앱은 자기 스키마에만 권한이 있는 계정으로 붙고, root 는 백업·관리용으로만 남긴다. | ||
| DB_ROOT_PASSWORD="$(ssm_param db-root-password)" || { echo "[mysql] SSM db-root-password 조회 실패"; exit 1; } | ||
| echo "[mysql] DB 자격증명 SSM 로드 완료 (${SSM_PREFIX}/db-*)" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER"; then | ||
| echo "[mysql] ${CONTAINER} 이미 존재 — skip" | ||
| else | ||
| echo "[mysql] ${CONTAINER} 기동 (named volume: ${VOLUME})" | ||
| # 포트는 모든 인터페이스에 연다. dev 박스가 172.17.0.1 로 묶는 것과 달리 여기서는 앱이 다른 | ||
| # 박스에서 사설 IP 로 접속하기 때문이다. 노출 범위는 보안그룹이 책임진다 — 3306 인바운드는 | ||
| # 앱 EC2 SG 에서 온 것만 허용한다(terraform/db_ec2.tf). | ||
| # | ||
| # MYSQL_ROOT_HOST=localhost 를 명시하는 이유: 이미지 기본값이 `%` 라 그냥 두면 네트워크 너머에서도 | ||
| # root 로 붙을 수 있는 root@% 계정이 생긴다. 보안그룹이 앱 박스만 통과시키더라도, 앱 박스가 뚫리면 | ||
| # 그대로 DB 전체 권한이 넘어간다. root 를 쓰는 곳은 컨테이너 안에서 도는 백업 스크립트뿐이라 | ||
| # localhost 로 좁혀도 잃는 게 없다. | ||
| # | ||
| # 아래 세 튜닝은 기본값이 이 박스 크기에 안 맞아서 넣는다. 기본값으로 띄웠을 때 실측이 | ||
| # RAM 234MB + swap 185MB = 약 419MB 로 캡(384m)을 넘어 상시 스왑 상태였다. | ||
| # --performance-schema=OFF 기본 ON 이고 이 구성에서 가장 큰 몫이다. 앱 지표는 Micrometer· | ||
| # Grafana 로 따로 받고 있어 DB 내부 계측을 켜 둘 실익이 없다. | ||
| # (진단이 필요하면 그때 켜서 재기동한다.) | ||
| # --innodb-buffer-pool-size 기본 128M. 데이터 총량이 2.1MB 라 64M 에도 전량이 캐시된다. | ||
| # --max-connections 기본 151. 앱 HikariCP 는 기본 풀 10 이고 blue-green 전환 때만 | ||
| # 잠시 2배가 되므로, 백업·관리 여유를 포함해도 60 이면 남는다. | ||
| # 값을 바꾸면 컨테이너 재생성이 필요하다(옵션은 기동 인자다). 데이터는 named volume 에 있어 | ||
| # 재생성 자체는 안전하지만, 재생성 동안 앱 연결이 끊기므로 배포 창을 잡아 수행한다. | ||
| docker run -d \ | ||
| --name "$CONTAINER" \ | ||
| --restart unless-stopped \ | ||
| --memory "$MEM_LIMIT" \ | ||
| --memory-swap "$MEM_SWAP" \ | ||
| -p 3306:3306 \ | ||
| -v "${VOLUME}:/var/lib/mysql" \ | ||
| -e MYSQL_DATABASE="$DB_NAME" \ | ||
| -e MYSQL_USER="$DB_USERNAME" \ | ||
| -e MYSQL_PASSWORD="$DB_PASSWORD" \ | ||
| -e MYSQL_ROOT_PASSWORD="$DB_ROOT_PASSWORD" \ | ||
| -e MYSQL_ROOT_HOST=localhost \ | ||
| "$MYSQL_IMAGE" \ | ||
| --performance-schema=OFF \ | ||
| --innodb-buffer-pool-size=64M \ | ||
| --max-connections=60 | ||
| fi | ||
|
|
||
| # readiness — 최초 기동은 DB·유저 생성 후 내부 재시작이 있어, 바로 접속하면 실패한다. | ||
| echo "[mysql] readiness 대기" | ||
| for i in $(seq 1 30); do | ||
| if docker exec "$CONTAINER" mysqladmin ping -h 127.0.0.1 --silent 2>/dev/null; then | ||
| echo "[mysql] ready (attempt $i)" | ||
| break | ||
| fi | ||
| sleep 2 | ||
| [ "$i" -eq 30 ] && { echo "[mysql] readiness timeout (60s)"; exit 1; } | ||
| done | ||
|
|
||
| # 자격증명이 실제로 통하는지 확인한다. 위 ping 은 서버 응답만 보고 인증을 안 거치므로, | ||
| # 그것만으로 "프로비저닝 성공"을 말하면 거짓이 될 수 있다. | ||
| # | ||
| # 이 검사가 필요한 이유: MYSQL_* 환경변수는 컨테이너를 "최초 생성"할 때만 쓰인다. 그 뒤 SSM 값을 | ||
| # 바꾸면 DB 안 계정은 옛 비밀번호로 남고 SSM 만 새 값이 되어, 둘이 조용히 갈라진다. 그 상태로 | ||
| # 스크립트가 성공하면 다음에 그 자격증명을 쓰는 쪽(앱 배포·백업 cron)이 대신 터진다. | ||
| # | ||
| # 어긋났을 때 여기서 자동으로 회전하지는 않는다. 앱 계정 비밀번호를 스크립트가 임의로 바꾸면 | ||
| # 붙어 있던 커넥션이 끊기므로, 회전은 사람이 배포 창에서 절차대로 수행할 일이다. | ||
| for role in root app; do | ||
| case "$role" in | ||
| root) user=root; pw="$DB_ROOT_PASSWORD" ;; | ||
| app) user="$DB_USERNAME"; pw="$DB_PASSWORD" ;; | ||
| esac | ||
| if ! docker exec -e MYSQL_PWD="$pw" "$CONTAINER" mysql -u "$user" -e "SELECT 1" >/dev/null 2>&1; then | ||
| echo "[mysql] ${role}(${user}) 자격증명이 SSM 값과 어긋난다 — 컨테이너 안 계정은 최초 생성 시의 비밀번호를 유지한다." | ||
| echo "[mysql] 회전 절차: docker exec -it ${CONTAINER} mysql -uroot -p 로 접속해" | ||
| echo "[mysql] ALTER USER '<user>'@'<host>' IDENTIFIED BY '<SSM 값>'; 을 실행한 뒤 이 스크립트를 다시 돌린다." | ||
| exit 1 | ||
| fi | ||
| done | ||
| echo "[mysql] 자격증명 검증 완료 (root·${DB_USERNAME} 모두 SSM 값으로 접속 가능)" | ||
|
|
||
| # ── 2) 백업 스크립트 + cron ───────────────────────────────────────────────── | ||
| # RDS 의 자동 백업을 대신하는 유일한 복구 경로다. 매 실행마다 최신본으로 덮어써 | ||
| # repo 의 스크립트와 박스의 것이 어긋나지 않게 한다. | ||
| if [ ! -f "${SCRIPT_DIR}/db-backup.sh" ]; then | ||
| echo "[backup] ${SCRIPT_DIR}/db-backup.sh 가 없다 — provision-db.sh 와 함께 올렸는지 확인" | ||
| exit 1 | ||
| fi | ||
| sudo install -m 0755 "${SCRIPT_DIR}/db-backup.sh" /usr/local/bin/piki-db-backup.sh | ||
| echo "[backup] /usr/local/bin/piki-db-backup.sh 설치" | ||
|
|
||
| # 19:00 UTC = 04:00 KST. 트래픽이 가장 적은 시간대에 둔다(백업 자체는 무중단이지만 | ||
| # 혹시 모를 부하도 한산할 때 지나가게 한다). | ||
| # 출력은 journald 로 보내 `journalctl -t piki-db-backup` 으로 성공·실패를 추적한다 — | ||
| # cron 기본 동작인 로컬 메일은 이 박스에 MTA 가 없어 사라진다. | ||
| # | ||
| # bash -o pipefail 로 감싸는 이유: 파이프라인의 종료 코드는 마지막 명령(logger)의 것이라, | ||
| # 그냥 파이프하면 백업이 실패해도 cron 이 보는 결과는 항상 성공이 된다. logger 는 거의 언제나 | ||
| # 0 을 반환하기 때문이다. pipefail 이 있어야 백업 스크립트의 실패 코드가 그대로 올라와, | ||
| # 나중에 실패 감지(cron 모니터링·systemd timer 등)를 붙일 때 그것이 실제로 동작한다. | ||
| CRON_LINE='0 19 * * * /bin/bash -o pipefail -c "/usr/local/bin/piki-db-backup.sh 2>&1 | /usr/bin/logger -t piki-db-backup"' | ||
| # 기존 항목을 걷어낸 뒤 최신 줄을 다시 넣는다 — 등록/갱신을 가르지 않아야 멱등이 단순해진다. | ||
| # | ||
| # `|| true` 가 필요한 이유: crontab 에 우리 줄만 있으면 grep -v 의 출력이 0건이라 종료 코드가 1 이고, | ||
| # set -euo pipefail 이 그걸 실패로 보고 스크립트를 끊는다. 즉 이 가드가 없으면 "두 번째 실행부터 | ||
| # 조용히 죽는" 스크립트가 된다(첫 실행은 crontab 이 비어 이 경로를 안 타므로 드러나지 않는다). | ||
| echo "[backup] cron 등록·갱신 (매일 19:00 UTC = 04:00 KST)" | ||
| { sudo crontab -l 2>/dev/null | grep -vF 'piki-db-backup.sh' || true; echo "$CRON_LINE"; } | sudo crontab - | ||
|
|
||
| # ── 3) 관측 수집기(alloy) ─────────────────────────────────────────────────── | ||
| # RDS 를 걷어내면서 잃은 것을 메운다. 관리형일 때는 CloudWatch 가 메모리·연결 수·디스크 여유· | ||
| # 쿼리 지연을 자동으로 줬는데, EC2 기본 메트릭에는 메모리조차 없다. 이 박스가 죽어가는 걸 | ||
| # 알아챌 수단이 아예 없어지는 셈이라, 다른 박스(core·extractor·renderer)와 같은 수집기를 붙인다. | ||
| # | ||
| # 이 박스에서 실제로 걷히는 것은 호스트 메트릭(CPU·메모리·디스크)뿐이다. 블록의 수집 대상은 | ||
| # docker label(piki.observe) opt-in 인데 MySQL 컨테이너에는 그 라벨이 없다 — redis·mysql 로그를 | ||
| # 빼는 기존 정책과 같은 결이라 여기서도 유지한다(로그 볼륨). MySQL 내부 지표(연결 수·쿼리 지연)가 | ||
| # 필요해지면 mysqld_exporter 를 따로 붙이는 별도 작업이다. | ||
| ALLOY_DIR="${SCRIPT_DIR}/alloy" | ||
| if [ "$SKIP_ALLOY" = true ]; then | ||
| echo "[alloy] --skip-alloy 지정 — 수집기 설치를 건너뛴다" | ||
| elif [ ! -f "${ALLOY_DIR}/provision-alloy.sh" ]; then | ||
| # 조용히 건너뛰지 않는다. 관측이 빠진 채 "프로비저닝 완료"가 찍히면 그 공백을 아무도 모른다 — | ||
| # 실제로 이 박스는 관측 없이 며칠 돌 뻔했다. 의도적으로 생략할 때만 --skip-alloy 로 명시한다. | ||
| echo "[alloy] ${ALLOY_DIR}/provision-alloy.sh 가 없다 — infra 의 blocks/alloy 를 함께 올릴 것" | ||
| echo "[alloy] 의도적으로 생략하려면 --skip-alloy 를 붙여 실행한다" | ||
| exit 1 | ||
| else | ||
| obs_param() { | ||
| docker run --rm --network host "$AWSCLI_IMAGE" ssm get-parameter \ | ||
| --name "/piki/observability/$1" --with-decryption \ | ||
| --region "$REGION" --query Parameter.Value --output text | ||
| } | ||
| # 필수 5종은 하나라도 없으면 중단한다. 빈 값으로 넘기면 provision-alloy.sh 가 skip(성공 0)으로 | ||
| # 조용히 지나가, 관측이 없는 상태가 "설치 완료"로 보고된다. | ||
| GRAFANA_METRICS_URL="$(obs_param grafana-metrics-url)" || { echo "[alloy] SSM grafana-metrics-url 조회 실패"; exit 1; } | ||
| GRAFANA_METRICS_USER="$(obs_param grafana-metrics-user)" || { echo "[alloy] SSM grafana-metrics-user 조회 실패"; exit 1; } | ||
| GRAFANA_LOGS_URL="$(obs_param grafana-logs-url)" || { echo "[alloy] SSM grafana-logs-url 조회 실패"; exit 1; } | ||
| GRAFANA_LOGS_USER="$(obs_param grafana-logs-user)" || { echo "[alloy] SSM grafana-logs-user 조회 실패"; exit 1; } | ||
| GRAFANA_CLOUD_TOKEN="$(obs_param grafana-cloud-token)" || { echo "[alloy] SSM grafana-cloud-token 조회 실패"; exit 1; } | ||
| # 트레이스는 이 박스에 앱이 없어 쓰이지 않는다. 빈 값이어도 블록이 무해 처리한다. | ||
| GRAFANA_TRACES_URL="$(obs_param grafana-traces-url)" || GRAFANA_TRACES_URL="" | ||
| GRAFANA_TRACES_USER="$(obs_param grafana-traces-user)" || GRAFANA_TRACES_USER="" | ||
| export GRAFANA_METRICS_URL GRAFANA_METRICS_USER GRAFANA_LOGS_URL GRAFANA_LOGS_USER | ||
| export GRAFANA_TRACES_URL GRAFANA_TRACES_USER GRAFANA_CLOUD_TOKEN | ||
| echo "[alloy] Grafana 자격 SSM 로드 완료" | ||
|
|
||
| # --box piki-db: 호스트 메트릭에는 service/instance 축이 없어, 같은 environment 의 박스들을 | ||
| # 이 라벨로만 가른다. 대시보드에서 DB 박스를 골라 보려면 이 값이 유일한 구분자다. | ||
| bash "${ALLOY_DIR}/provision-alloy.sh" \ | ||
| --config "${ALLOY_DIR}/config.alloy" \ | ||
| --name piki-alloy \ | ||
| --environment prod \ | ||
| --box piki-db | ||
| fi | ||
|
|
||
| echo "DB 박스 프로비저닝 완료" | ||
| echo " 백업 수동 실행: sudo /usr/local/bin/piki-db-backup.sh" | ||
| echo " 백업 로그 확인: journalctl -t piki-db-backup --since '1 day ago'" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.