# infra scripts 기준 ## 목적 이 문서는 1000+ 서비스 플랫폼에서 인프라 스크립트가 지켜야 할 품질 기준선이다. 스크립트는 선언형 원본 (Kustomize / Helm / ArgoCD / Flux) 을 **대체하지 않는다**. 렌더 / diff / 적용 / 백업 / 복구 / 부트스트랩을 **orchestration** 하는 얇은 레이어로 제한한다. ## 공식 / 업계 근거 - **Google Shell Style Guide**: `#!/usr/bin/env bash`, `set -e`, `main "$@"`, function-first, `local`. - **Unofficial Bash Strict Mode (Aaron Maxwell)**: `set -euo pipefail` + `IFS=$'\n\t'` 가 사실상 표준. - **ShellCheck** (https://www.shellcheck.net/): 정적 분석. CI에서 mandatory. - **shfmt** (mvdan/sh): 자동 포맷터. line-length / indent 규격 강제. - **GitOps 원칙** (Weaveworks 정의): 선언형 원본 + auto-reconcile. 스크립트는 원본을 소유하지 않는다. ## 기본 규칙 ### 1. 모든 스크립트 맨 위에 strict mode ```bash #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' ``` 의미: - `set -e` : 명령 실패 시 즉시 종료. - `set -u` : unset variable 참조 시 에러. - `set -o pipefail` : pipeline 중 하나라도 실패하면 전체 실패. - `IFS=$'\n\t'` : 기본 IFS에서 space 제거 → 파일명 공백 sane split. 예외 금지. CI lint 에서 검사. ### 2. 정리 작업은 `trap` 으로 보장 임시 파일 / 임시 kubeconfig / port-forward / background job 은 반드시 trap EXIT 에서 정리. ```bash TMPDIR="$(mktemp -d)" trap 'rm -rf "${TMPDIR}"' EXIT INT TERM ``` - `EXIT`: 정상/비정상 종료 모두 잡음. - `INT TERM`: signal 기반 종료 시에도 실행. - trap은 setup 직후 즉시 설치. ### 3. ShellCheck + shfmt 는 CI 에서 필수 - `shellcheck -S style scripts/**/*.sh` → CI fail 시 merge 금지. - `shfmt -i 2 -bn -ci -d scripts/` → 자동 포맷 검증. - suppress (`# shellcheck disable=...`) 는 **줄 단위**로만, 이유 주석 필수. - "경고 너무 많아서 꺼둔다" 금지. ### 4. 표준 `log()` 함수 (ISO 8601 timestamp + level, stderr) ```bash log() { local level="$1"; shift local ts ts="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" printf '%s [%s] %s\n' "${ts}" "${level}" "$*" >&2 } info() { log INFO "$@"; } warn() { log WARN "$@"; } error() { log ERROR "$@"; } fatal() { log FATAL "$@"; exit 1; } ``` - stdout 은 머신 판독용 결과 전용. - stderr 로 로그 → pipeline 안전. - UTC ISO 8601 로 tz 모호성 제거. ### 5. 엔트리포인트 `main "$@"` 패턴 ```bash usage() { cat <<'EOF' >&2 Usage: render-diff-apply.sh [--overlay PATH] [--context NAME] [--yes] --overlay PATH path to kustomize overlay (required) --context NAME kube context (required) --yes skip confirmation for apply EOF } main() { # 인자 파싱 # 환경 검증 # 함수 호출 : } main "$@" ``` - 엔트리포인트 스크립트는 **얇게**. 비즈니스 로직은 `lib/` 또는 `tasks/`. - `usage()` 함수 필수. ### 6. 변수는 `local`, command substitution 은 분리 ```bash bad_pattern() { local ctx="$(kubectl config current-context)" # local 이 exit status 가려버림 } good_pattern() { local ctx ctx="$(kubectl config current-context)" # 분리 → $? 보존 } ``` ShellCheck SC2155 가 이것을 잡음. ### 7. Idempotent 를 기본값으로 - `create` 보다 `apply` / `ensure` 성격. - `kubectl apply -k` 는 idempotent. - `mkdir -p`, `kubectl create namespace X --dry-run=client -o yaml | kubectl apply -f -` 패턴. - destroy 성격은 반드시 opt-in. ### 8. `kubectl diff` → `kubectl apply` 필수 흐름 프로덕션 적용 스크립트 기본 흐름: ``` 1. kubectl kustomize > render.yaml # render 2. kubeconform / kubectl apply --dry-run=server # validate 3. kubectl diff -k # preview 4. confirm gate (CONFIRM=yes 또는 --yes) 5. kubectl apply -k # apply 6. kubectl rollout status ... --timeout=10m # watch ``` ### 9. `--dry-run=server` 를 validation 기본값으로 client-side dry run 은 CRD schema / admission webhook 을 평가하지 않는다. **server-side dry run** 을 쓴다: ```bash kubectl apply -k "${OVERLAY}" --dry-run=server ``` ### 10. destructive 작업은 `--yes` 또는 `CONFIRM=yes` gate delete / prune / restore overwrite 류는 명시적 opt-in 없이 실행 금지. ```bash if [[ "${CONFIRM:-no}" != "yes" ]]; then fatal "destructive operation requires CONFIRM=yes" fi ``` 또는: ```bash if [[ "${YES:-0}" -ne 1 ]]; then warn "re-run with --yes to confirm" exit 2 fi ``` ### 11. 환경을 암묵적으로 추론하지 않는다 - 대상 overlay / namespace / context 는 **명시적 인자**로. - `kubectl config current-context` 에 몰래 의존 금지. - 필요한 env var 는 시작 시 `[[ -z "${FOO:-}" ]] && fatal "FOO required"` 로 검증. ### 12. JSON 파싱은 `jq` / `kubectl -o jsonpath`, 절대 regex 로 하지 않는다 ```bash # BAD kubectl get pod foo -o yaml | grep "image:" | awk '{print $2}' # GOOD kubectl get pod foo -o jsonpath='{.spec.containers[0].image}' # GOOD kubectl get pod foo -o json | jq -r '.spec.containers[0].image' ``` kubectl/kubernetes 출력에 regex 쓰면 field 순서 / 라벨 / 버전 변화에 깨진다. ### 13. 비밀값은 로그 / stdout / 파일에 남기지 않는다 - env var / secret value 를 `set -x` 아래에서 직접 사용 금지. - debug 모드에서는 masking: ```bash mask_secrets() { sed -E \ -e 's/(password=)[^ ]+/\1***/g' \ -e 's/(token=)[^ ]+/\1***/g' \ -e 's/(Authorization: Bearer )[A-Za-z0-9._-]+/\1***/g' } some_command --debug | mask_secrets ``` - secret 을 참조해야 하면 `--from-file` 이나 stdin pipe 로 주입, argv 금지. ### 14. 스크립트는 선언형 원본을 소유하지 않는다 **금지**: - 대규모 heredoc YAML 생성기 (스크립트 내부에 매니페스트 숨김). - 환경별 로직이 if/else 로만 존재. - 스크립트만 실행해야 실제 상태를 알 수 있는 구조. **허용**: - `kubectl apply -k overlays/` wrapping. - Helm chart render + apply orchestration. - backup / restore (stateful data 만 대상). - bootstrap (namespace, secret store 설치 같은 일회성). - smoke test. ### 15. 폴더 구조 ```text scripts/ bin/ # 엔트리포인트 (얇게) render diff apply backup-k3s restore-k3s lib/ # 공통 함수 common.sh # log, fatal, require_cmd, confirm kubectl.sh # kubectl wrappers kustomize.sh # kustomize render helpers tasks/ # 도메인 작업 keycloak.sh vault.sh flyway.sh ci/ # CI 검증 전용 lint.sh validate.sh ``` - `bin/` 파일 이름은 동사. - `lib/` 는 20개 내외, 잡동사니 함수 금지. - 하나의 거대 `deploy.sh` 금지. ### 16. retry 는 함수화, 무한 루프 금지 ```bash retry() { local max="$1"; shift local delay="$1"; shift local n=0 until "$@"; do n=$((n + 1)) if (( n >= max )); then return 1 fi sleep "${delay}" done } retry 5 3 kubectl rollout status deployment/foo --timeout=30s ``` backoff 는 선형/지수 명시, 무한 retry 금지. ### 17. quoting / array 기본값 - 모든 변수 전개는 `"${VAR}"`. - 인자 list 는 array: `args=(--namespace foo --context bar)`. - `"$@"` 유지. - unquoted glob / word splitting 금지. ### 18. 출력 채널 규칙 - stdout → 머신 판독 결과 (jsonpath 결과, 렌더된 YAML 등). - stderr → 로그, 경고, 에러, 진행 표시. - exit code → 0 success, 1 error, 2 usage error. pipeline 하류 도구가 stdout 을 parse 한다는 전제로 작성. ## 프로젝트 기준 요약 - strict mode `set -euo pipefail` + `IFS=$'\n\t'` 필수. - trap EXIT INT TERM 으로 정리 보장. - ShellCheck + shfmt CI 필수. - ISO 8601 UTC + LEVEL 로그 함수 (stderr). - `main "$@"` 패턴 + usage() 함수. - local 선언과 command substitution 분리. - idempotent 기본, destructive 는 `--yes` / `CONFIRM=yes` gate. - `kubectl diff` → `apply`, `--dry-run=server` validation. - 환경 추론 금지, overlay/namespace/context 명시. - JSON 은 jq / jsonpath, 절대 regex 금지. - secret 은 log / argv 에 남기지 않고 masking. - 스크립트는 선언형 원본을 소유하지 않는 orchestration 레이어. - `bin/ lib/ tasks/ ci/` 폴더 분리, giant deploy.sh 금지.