294 lines
8.3 KiB
Markdown
294 lines
8.3 KiB
Markdown
# 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 <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** 을 쓴다:
|
|
|
|
```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/<env>` 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 금지.
|