init: 폴더구조 설계 및 인프라 설계
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
# 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 금지.
|
||||
Reference in New Issue
Block a user