Files
project-infra/docs/standards/infra/scripts.md
T

8.3 KiB

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

#!/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 에서 정리.

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)

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 "$@" 패턴

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 은 분리

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 diffkubectl apply 필수 흐름

프로덕션 적용 스크립트 기본 흐름:

1. kubectl kustomize <overlay> > render.yaml     # render
2. kubeconform / kubectl apply --dry-run=server  # validate
3. kubectl diff -k <overlay>                     # preview
4. confirm gate (CONFIRM=yes 또는 --yes)
5. kubectl apply -k <overlay>                    # 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 을 쓴다:

kubectl apply -k "${OVERLAY}" --dry-run=server

10. destructive 작업은 --yes 또는 CONFIRM=yes gate

delete / prune / restore overwrite 류는 명시적 opt-in 없이 실행 금지.

if [[ "${CONFIRM:-no}" != "yes" ]]; then
  fatal "destructive operation requires CONFIRM=yes"
fi

또는:

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 로 하지 않는다

# 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:
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/<env> wrapping.
  • Helm chart render + apply orchestration.
  • backup / restore (stateful data 만 대상).
  • bootstrap (namespace, secret store 설치 같은 일회성).
  • smoke test.

15. 폴더 구조

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 는 함수화, 무한 루프 금지

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 diffapply, --dry-run=server validation.
  • 환경 추론 금지, overlay/namespace/context 명시.
  • JSON 은 jq / jsonpath, 절대 regex 금지.
  • secret 은 log / argv 에 남기지 않고 masking.
  • 스크립트는 선언형 원본을 소유하지 않는 orchestration 레이어.
  • bin/ lib/ tasks/ ci/ 폴더 분리, giant deploy.sh 금지.