docs: record observability setup and add concept layers 10-13
Adds Kubernetes resources (StatefulSet, PVC, Secret, RBAC, placement), Keycloak clustering internals (Infinispan, JGroups), Prometheus concepts and virtualization operations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d5cc2b55a9
commit
006da7d490
@@ -2826,17 +2826,585 @@ SSH 공개키 두 줄이다. 공개키 자체는 비밀이 아니지만, **저
|
||||
|
||||
---
|
||||
|
||||
## 10층. 쿠버네티스 리소스 — 이 실험대에서 실제로 쓴 것들
|
||||
|
||||
5층이 k3s 자체라면 여기는 그 위에 올린 리소스들이다.
|
||||
|
||||
### 워크로드 세 종류 — 무엇을 언제 쓰는가
|
||||
|
||||
| | 보장하는 것 | 이 실험대에서 |
|
||||
|---|---|---|
|
||||
| **Deployment** | 파드 N개를 유지. 이름은 매번 바뀐다 | postgres, grafana, prometheus, echo |
|
||||
| **StatefulSet** | **안정된 이름**(`-0`, `-1`)과 순서 | **keycloak** |
|
||||
| **DaemonSet** | **노드마다 정확히 하나** | node-exporter, svclb |
|
||||
|
||||
**StatefulSet을 Keycloak에 쓴 이유** — Infinispan이 **파드 이름 + 랜덤 접미사**를
|
||||
클러스터 노드 식별자로 쓴다(`keycloak-0-49501`). Deployment면 이름이
|
||||
`keycloak-7d9f8b-x4k2p`처럼 매번 달라져서, 로그와 `JGROUPS_PING` 테이블을
|
||||
대조하기가 어려워진다.
|
||||
|
||||
**`podManagementPolicy`**
|
||||
|
||||
| 값 | 동작 |
|
||||
|---|---|
|
||||
| `OrderedReady` (기본) | `-0`이 Ready가 된 뒤에야 `-1`을 만든다 |
|
||||
| **`Parallel`** | **동시에 시작한다** |
|
||||
|
||||
이 실험대는 `Parallel`을 쓴다. 두 파드가 **동시에 클러스터 등록을 시도하는 것**이
|
||||
운영에서 실제로 일어나는 상황이기 때문이다.
|
||||
|
||||
**DaemonSet을 node-exporter에 쓴 이유** — replica 수를 지정하지 않는다.
|
||||
노드가 늘면 자동으로 늘고, 줄면 준다. **죽을 노드에도 반드시 있어야**
|
||||
꺼지기 직전의 마지막 샘플이 남는다.
|
||||
|
||||
```bash
|
||||
kubectl get deploy,sts,ds -A
|
||||
```
|
||||
|
||||
### 저장소 — PVC · PV · StorageClass
|
||||
|
||||
```
|
||||
PersistentVolumeClaim (PVC) "5Gi 짜리 읽기쓰기 볼륨을 주세요" ← 요청
|
||||
│ storageClassName: local-path
|
||||
▼
|
||||
StorageClass 어떻게 만들지 아는 프로비저너
|
||||
│
|
||||
▼
|
||||
PersistentVolume (PV) 실제로 만들어진 볼륨 ← 결과
|
||||
```
|
||||
|
||||
**PVC는 요청서, PV는 실물이다.** 파드는 PVC 이름만 알면 되고, 그 뒤가
|
||||
로컬 디스크인지 NFS인지 클라우드 블록 스토리지인지 몰라도 된다.
|
||||
|
||||
**`accessModes`**
|
||||
|
||||
| 값 | 의미 |
|
||||
|---|---|
|
||||
| **`ReadWriteOnce` (RWO)** | **한 노드에서만** 읽기/쓰기 |
|
||||
| `ReadOnlyMany` | 여러 노드에서 읽기만 |
|
||||
| `ReadWriteMany` | 여러 노드에서 읽기/쓰기 (NFS 등) |
|
||||
|
||||
**RWO가 `strategy: Recreate`를 강제한다.** 기본값 `RollingUpdate`는 새 파드를
|
||||
띄운 뒤 옛 파드를 내리는데, RWO 볼륨은 **두 파드가 동시에 마운트할 수 없어서**
|
||||
새 파드가 영원히 Pending에 머문다.
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
type: Recreate # 옛 파드를 먼저 내리고 새 파드를 띄운다
|
||||
```
|
||||
|
||||
**k3s의 `local-path` 프로비저너 — 볼륨이 노드에 못박힌다**
|
||||
|
||||
```json
|
||||
"nodeAffinity": {
|
||||
"required": { "nodeSelectorTerms": [{
|
||||
"matchExpressions": [{ "key": "kubernetes.io/hostname", "values": ["kc-lab-2"] }]
|
||||
}]}
|
||||
}
|
||||
경로: /var/lib/rancher/k3s/storage/pvc-<uuid>_<ns>_<name>
|
||||
```
|
||||
|
||||
**그 노드의 로컬 디스크에 디렉터리를 만드는 것이 전부**다. 따라서
|
||||
**PVC를 쓰는 파드는 그 노드를 벗어날 수 없다.**
|
||||
|
||||
| 결과 | |
|
||||
|---|---|
|
||||
| 노드가 죽으면 | **파드가 다른 노드로 재배치되지 못한다** |
|
||||
| 실험 관점 | **결함이 아니라 조건이다.** "DB가 있는 노드가 죽으면"이 의미를 갖는다 |
|
||||
|
||||
```bash
|
||||
kubectl get pvc -A
|
||||
kubectl get pv
|
||||
kubectl get pv <name> -o jsonpath='{.spec.nodeAffinity}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Secret — 감춰지지 않는다
|
||||
|
||||
```yaml
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
stringData:
|
||||
POSTGRES_PASSWORD: lab-postgres-change-me
|
||||
```
|
||||
|
||||
`stringData`는 평문으로 쓰고 쿠버네티스가 base64로 인코딩해 저장한다.
|
||||
`data`는 직접 base64로 넣는다.
|
||||
|
||||
**base64는 암호화가 아니라 인코딩이다.**
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d
|
||||
```
|
||||
|
||||
한 줄로 읽힌다. etcd에도 그대로 들어 있다.
|
||||
|
||||
| 그래도 Secret을 쓰는 이유 | |
|
||||
|---|---|
|
||||
| RBAC로 접근을 나눌 수 있다 | ConfigMap과 별도로 권한 관리 |
|
||||
| 로그·`describe`에 값이 안 찍힌다 | 사고로 노출될 확률이 준다 |
|
||||
| 볼륨·env 주입 방식이 표준화된다 | |
|
||||
|
||||
**진짜 보호는 별도 계층이다** — SealedSecret, 외부 KMS, 또는 클라우드
|
||||
시크릿 매니저. 로드맵 11번의 주제다.
|
||||
|
||||
### RBAC — ServiceAccount · ClusterRole · Binding
|
||||
|
||||
Prometheus가 쿠버네티스 API에 물어서 타깃을 찾으려면 **읽기 권한**이 필요하다.
|
||||
|
||||
```
|
||||
ServiceAccount 파드가 쓰는 신원 (누구인가)
|
||||
│
|
||||
ClusterRoleBinding 신원과 권한을 잇는다
|
||||
│
|
||||
ClusterRole 무엇을 할 수 있는가 (리소스 × 동사)
|
||||
```
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: [nodes, nodes/metrics, nodes/proxy, services, endpoints, pods]
|
||||
verbs: [get, list, watch]
|
||||
```
|
||||
|
||||
**`Role`과 `ClusterRole`의 차이** — `Role`은 한 네임스페이스 안에서만,
|
||||
`ClusterRole`은 클러스터 전체에서 유효하다. 노드는 네임스페이스에 속하지
|
||||
않으므로 **노드를 읽으려면 반드시 `ClusterRole`**이다.
|
||||
|
||||
**서브리소스가 따로 있다 — 실제로 걸린 함정**
|
||||
|
||||
`nodes`, `nodes/metrics`, `nodes/proxy`는 **서로 다른 권한**이다.
|
||||
|
||||
```
|
||||
/api/v1/nodes/<name>/proxy/metrics
|
||||
─────
|
||||
이 경로에는 nodes/proxy 가 필요
|
||||
```
|
||||
|
||||
`nodes/proxy`를 빠뜨렸을 때 kubelet 타깃만 **403 Forbidden**으로 실패하고
|
||||
나머지 잡은 전부 정상이었다. **부분 실패라 `rollout status`는 성공이라고
|
||||
말한다.** 타깃 목록을 직접 봐야 드러난다.
|
||||
|
||||
```bash
|
||||
kubectl auth can-i get nodes/proxy --as=system:serviceaccount:observability:prometheus
|
||||
kubectl describe clusterrole prometheus
|
||||
```
|
||||
|
||||
### 배치 제어 — nodeSelector · 라벨 · taint
|
||||
|
||||
```yaml
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: "true"
|
||||
```
|
||||
|
||||
**호스트 이름 대신 역할 라벨을 쓴다.** `kubernetes.io/hostname: kc-lab-1`로
|
||||
못박으면 노드 이름이 바뀔 때 깨지고, **왜 거기 두는지가 드러나지 않는다.**
|
||||
|
||||
k3s는 server 노드에 `node-role.kubernetes.io/control-plane=true`를 붙인다.
|
||||
|
||||
```bash
|
||||
kubectl get nodes --show-labels
|
||||
kubectl get nodes -l node-role.kubernetes.io/control-plane=true
|
||||
```
|
||||
|
||||
**taint와 toleration**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **taint** | 노드에 붙는 "여기 오지 마" 표시 |
|
||||
| **toleration** | 파드가 갖는 "그래도 갈 수 있음" 면제권 |
|
||||
|
||||
```yaml
|
||||
tolerations:
|
||||
- operator: Exists # 어떤 taint 든 무시한다
|
||||
```
|
||||
|
||||
node-exporter에 이걸 주는 이유는 **관측이 빠지는 노드가 있으면 안 되기**
|
||||
때문이다. taint가 걸린 노드에서도 떠야 한다.
|
||||
|
||||
**배치를 정하는 세 수단의 차이**
|
||||
|
||||
| 수단 | 성격 |
|
||||
|---|---|
|
||||
| `nodeSelector` | **반드시** 그 라벨의 노드에 |
|
||||
| `topologySpreadConstraints` | **골고루** 퍼뜨린다 |
|
||||
| taint / toleration | 노드가 **거부**하고 파드가 **면제**받는다 |
|
||||
|
||||
### k3s server와 agent — 죽였을 때가 다르다
|
||||
|
||||
```bash
|
||||
kubectl get nodes -o custom-columns=\
|
||||
'NODE:.metadata.name,CP:.metadata.labels.node-role\.kubernetes\.io/control-plane'
|
||||
```
|
||||
|
||||
| | kc-lab-1 (**server**) | kc-lab-2 (**agent**) |
|
||||
|---|---|---|
|
||||
| 실행 | API 서버 · 스케줄러 · etcd(SQLite) | kubelet · containerd |
|
||||
| 이 실험대에서 | keycloak-1 · traefik · **coredns** · metrics-server · local-path-provisioner | keycloak-0 · postgres |
|
||||
| 죽이면 | **`kubectl`이 안 된다. DNS·인그레스도 사라진다** | 클러스터 제어는 살아 있다 |
|
||||
|
||||
**노드 상실 실험은 agent를 죽이는 것이다.** server를 죽이는 것은 노드 상실이
|
||||
아니라 **컨트롤 플레인 상실**이며 성격이 완전히 다르다.
|
||||
|
||||
이 사실을 모르고 "keycloak 하나만 있는 노드를 죽이자"고 계획했다가
|
||||
실제 배치를 조회한 뒤 정정했다.
|
||||
|
||||
---
|
||||
|
||||
## 11층. Keycloak 클러스터링 내부 — Infinispan과 JGroups
|
||||
|
||||
### 두 층으로 되어 있다
|
||||
|
||||
```
|
||||
Infinispan 분산 캐시. "세션을 어디에 두고 어떻게 복제할까"
|
||||
│
|
||||
JGroups 그룹 통신. "누가 멤버이고 어떻게 메시지를 주고받을까"
|
||||
│
|
||||
TCP 7800 실제 소켓
|
||||
```
|
||||
|
||||
Keycloak은 Infinispan을 쓰고, Infinispan은 JGroups 위에서 돈다.
|
||||
로그의 `org.infinispan.CLUSTER`와 `vendor_jgroups_*` 지표가 각각 이 두 층이다.
|
||||
|
||||
### 디스커버리와 트랜스포트는 다른 경로다
|
||||
|
||||
**이것이 이 실험대를 2노드로 만든 이유다.**
|
||||
|
||||
| 단계 | 경로 | 끊기면 |
|
||||
|---|---|---|
|
||||
| **디스커버리** — 서로를 찾는다 | PostgreSQL `JGROUPS_PING` 테이블 | 상대의 존재를 모른다 |
|
||||
| **트랜스포트** — 실제로 대화한다 | **TCP 7800** | **DB엔 등록되는데 클러스터가 안 붙는다** |
|
||||
|
||||
`JGROUPS_PING` 한 테이블에 두 메커니즘이 다 보인다.
|
||||
|
||||
```
|
||||
name | cluster_name | ip | coord
|
||||
------------------+--------------+-----------------+-------
|
||||
keycloak-0-49501 | ISPN | 10.42.1.18:7800 | f
|
||||
keycloak-1-26938 | ISPN | 10.42.0.16:7800 | t
|
||||
───────────────────────────── ──── ─
|
||||
디스커버리 결과 트랜스포트 경로 코디네이터
|
||||
```
|
||||
|
||||
전체 스키마는 `address / name / cluster_name / ip / coord / last_update /
|
||||
coordinated_by`이고 기본키는 `address`다.
|
||||
|
||||
> 오래된 자료에는 `own_addr`, `ping_data` 같은 컬럼명이 나오지만 Keycloak 26의
|
||||
> 실제 스키마는 위와 같다. 쿼리 전에 `\d jgroups_ping`으로 확인한다.
|
||||
|
||||
**`jdbc-ping`을 쓰는 이유** — 예전에는 UDP 멀티캐스트로 서로를 찾았다.
|
||||
쿠버네티스나 클라우드에서는 멀티캐스트가 막혀 있는 경우가 많아,
|
||||
**이미 있는 데이터베이스를 게시판처럼 쓰는** 방식으로 바뀌었다.
|
||||
Keycloak 26의 기본값이다.
|
||||
|
||||
### 코디네이터
|
||||
|
||||
`coord = t` 인 노드가 **코디네이터**다. 뷰 변경을 확정하고 리밸런싱을
|
||||
주도한다. 특별한 권한이 아니라 **역할**이며, 그 노드가 사라지면 남은 멤버가
|
||||
인계받는다.
|
||||
|
||||
실험대를 전원 종료했다 켰을 때 코디네이터가 `keycloak-1` → `keycloak-0`으로
|
||||
바뀌는 것을 관찰했다. **먼저 뜬 쪽이 맡는다.**
|
||||
|
||||
### 클러스터 뷰
|
||||
|
||||
```
|
||||
ISPN000094: Received new cluster view for channel ISPN:
|
||||
[keycloak-1-26938(v=16.0.12)|1] (2) [keycloak-1-26938, keycloak-0-49501]
|
||||
─────────────────────────── ─ ─ ────────────────────────────────────
|
||||
뷰를 만든 코디네이터 뷰 ID 멤버 수 멤버 목록
|
||||
```
|
||||
|
||||
**뷰(view)는 "지금 이 순간의 멤버 명단"** 이다. 멤버가 들어오거나 나가면
|
||||
새 뷰가 발행되고 뷰 ID가 올라간다.
|
||||
|
||||
| 로그 코드 | 의미 |
|
||||
|---|---|
|
||||
| `ISPN000094` | 새 클러스터 뷰를 받았다 |
|
||||
| `ISPN000079` | 자기 주소와 물리 주소(7800) |
|
||||
| `ISPN100000` | 노드가 합류했다 |
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab logs keycloak-0 | grep -E 'ISPN000094|ISPN000079|ISPN100000'
|
||||
```
|
||||
|
||||
### 주요 JGroups 프로토콜 — 지표 이름에 그대로 나온다
|
||||
|
||||
| 프로토콜 | 하는 일 | 관련 지표 |
|
||||
|---|---|---|
|
||||
| **GMS** (Group Membership Service) | 멤버십 관리, 뷰 발행 | `vendor_jgroups_gms_*` |
|
||||
| **FD_SOCK2** (Failure Detection) | **TCP 소켓으로 상대 생존 감시** | `..._get_num_suspected_members` |
|
||||
| **MERGE3** | **split brain 후 다시 합치기** | `..._merge3_get_views` |
|
||||
| **NAKACK2** | 신뢰성 있는 메시지 전달, 재전송 | `..._nakack2_*` |
|
||||
| **TCP** | 트랜스포트 | `..._tcp_*` |
|
||||
|
||||
**7800을 막으면 FD_SOCK2가 먼저 반응한다.** 소켓 연결이 끊기면 상대를
|
||||
suspect 하고, GMS가 그 멤버를 뷰에서 제외한다. 각자 자기만 있는 뷰가 되면
|
||||
**split brain**이고, 통신이 복구되면 MERGE3가 합친다.
|
||||
|
||||
### 세션은 어디에 있는가 — 두 곳 다
|
||||
|
||||
Keycloak 26의 기본값 `persistent-user-sessions`에서는
|
||||
|
||||
| 저장소 | 역할 |
|
||||
|---|---|
|
||||
| **PostgreSQL** | **진실의 원천.** 재시작에도 살아남는다 |
|
||||
| **Infinispan** | 캐시 + 노드 간 실시간 전파 |
|
||||
|
||||
`--features-disabled=persistent-user-sessions`로 끄면 Infinispan만 남는
|
||||
**volatile** 모드가 되고, 그때는 캐시가 곧 진실의 원천이다.
|
||||
이 둘의 차이가 로드맵 2번의 주제다.
|
||||
|
||||
---
|
||||
|
||||
## 12층. 관측성 — Prometheus의 구조
|
||||
|
||||
### 세 부분으로 되어 있다
|
||||
|
||||
```
|
||||
수집(scrape) ──▶ 저장(TSDB) ──▶ 질의(PromQL)
|
||||
15초마다 로컬 디스크 Grafana 또는 API
|
||||
HTTP GET /metrics 시계열
|
||||
```
|
||||
|
||||
**Prometheus는 pull 방식이다.** 대상이 보내주는 것이 아니라 Prometheus가
|
||||
주기적으로 `/metrics`를 긁어간다.
|
||||
|
||||
| 결과 | |
|
||||
|---|---|
|
||||
| 대상이 죽으면 | 긁기가 실패하고 **`up`이 0이 된다** — 죽은 사실 자체가 데이터가 된다 |
|
||||
| 방화벽 방향 | Prometheus → 대상. 대상이 Prometheus 주소를 알 필요가 없다 |
|
||||
| 짧은 작업 | 긁히기 전에 끝나면 잡히지 않는다 (Pushgateway가 필요한 경우) |
|
||||
|
||||
### exporter 패턴
|
||||
|
||||
애플리케이션이 Prometheus 형식을 모를 때, **번역기**를 옆에 둔다.
|
||||
|
||||
| exporter | 무엇을 노출하는가 |
|
||||
|---|---|
|
||||
| **node-exporter** | 머신 — CPU, 메모리, 디스크, 네트워크 |
|
||||
| kube-state-metrics | 쿠버네티스 오브젝트 상태 |
|
||||
| postgres-exporter | PostgreSQL 내부 통계 |
|
||||
|
||||
**Keycloak과 Traefik은 exporter가 필요 없다.** 자체적으로 Prometheus 형식
|
||||
엔드포인트를 제공한다(`KC_METRICS_ENABLED=true`).
|
||||
|
||||
### 서비스 디스커버리 — 타깃을 적어두지 않는다
|
||||
|
||||
```yaml
|
||||
kubernetes_sd_configs:
|
||||
- role: endpoints
|
||||
namespaces: { names: [keycloak-lab] }
|
||||
```
|
||||
|
||||
**파드 IP는 재시작마다 바뀐다.** 실험대를 전원 종료했다 켜니 모든 파드가
|
||||
새 주소를 받았다(`10.42.1.22` → `10.42.1.25`). 정적 목록은 그때마다 깨진다.
|
||||
|
||||
`role`에 따라 무엇을 찾을지가 달라진다.
|
||||
|
||||
| role | 찾는 것 |
|
||||
|---|---|
|
||||
| `endpoints` | 서비스 뒤의 실제 파드들 ← 애플리케이션 지표 |
|
||||
| `node` | 노드 |
|
||||
| `pod` | 파드 직접 |
|
||||
| `service` | 서비스 |
|
||||
|
||||
### relabel — 걸러내고 이름을 붙인다
|
||||
|
||||
디스커버리는 **전부 다** 가져온다. 그중 필요한 것만 남기는 것이 relabel이다.
|
||||
|
||||
```yaml
|
||||
relabel_configs:
|
||||
- source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
|
||||
action: keep
|
||||
regex: keycloak-headless;management
|
||||
- source_labels: [__meta_kubernetes_pod_name]
|
||||
target_label: pod
|
||||
```
|
||||
|
||||
| `action` | 하는 일 |
|
||||
|---|---|
|
||||
| `keep` | regex에 맞는 것만 남긴다 |
|
||||
| `drop` | 맞는 것을 버린다 |
|
||||
| `replace` (기본) | 라벨 값을 만든다 |
|
||||
| `labelmap` | 메타 라벨을 일반 라벨로 복사 |
|
||||
|
||||
**`__`로 시작하는 라벨은 내부용**이며 저장되지 않는다. `__meta_*`는
|
||||
디스커버리가 붙여준 정보이고, 필요하면 `target_label`로 옮겨야 남는다.
|
||||
|
||||
**`pod`과 `node` 라벨을 붙이는 것이 실험에서 결정적이다.** 없으면
|
||||
"어느 파드가, 어느 노드에서"에 답할 수 없다.
|
||||
|
||||
### 메트릭 타입
|
||||
|
||||
| 타입 | 성질 | 예 |
|
||||
|---|---|---|
|
||||
| **counter** | **누적. 줄지 않는다** (재시작 시 0으로) | `..._requests_total` |
|
||||
| **gauge** | 오르내린다 | `node_memory_MemAvailable_bytes` |
|
||||
| **histogram** | 구간별 분포 + 합계 + 개수 | `..._seconds_bucket/_sum/_count` |
|
||||
| summary | 분위수를 클라이언트가 계산 | |
|
||||
|
||||
**counter는 그대로 보면 의미가 없다.** 변화율을 봐야 한다.
|
||||
|
||||
```promql
|
||||
rate(http_requests_total[5m])
|
||||
```
|
||||
|
||||
**histogram은 세 지표가 한 벌**이다. `_bucket`으로 분위수를 계산한다.
|
||||
|
||||
```promql
|
||||
histogram_quantile(0.95, rate(keycloak_session_expiration_task_seconds_bucket[5m]))
|
||||
```
|
||||
|
||||
### `up` — 가장 중요한 합성 지표
|
||||
|
||||
```promql
|
||||
up
|
||||
up{job="keycloak"}
|
||||
```
|
||||
|
||||
Prometheus가 **직접 만드는** 지표다. 긁기에 성공하면 1, 실패하면 0.
|
||||
|
||||
**장애 실험에서 이것이 핵심인 이유** — 다른 지표는 대상이 죽으면 **사라진다.**
|
||||
사라진 데이터로는 "언제부터 죽었나"를 알 수 없다. `up`은 **0이라는 값으로
|
||||
남기 때문에** 사후에 시각을 특정할 수 있다.
|
||||
|
||||
```promql
|
||||
up == 0 # 지금 죽은 타깃
|
||||
changes(up[1h]) # 1시간 동안 몇 번 오르내렸나
|
||||
min_over_time(up[10m]) # 10분 중 한 번이라도 죽었나
|
||||
```
|
||||
|
||||
### TSDB와 보존 기간
|
||||
|
||||
```yaml
|
||||
--storage.tsdb.path=/prometheus
|
||||
--storage.tsdb.retention.time=7d
|
||||
```
|
||||
|
||||
로컬 디스크에 시계열로 저장한다. **보존 기간이 지나면 삭제**되므로 볼륨이
|
||||
무한히 커지지 않는다.
|
||||
|
||||
`emptyDir`에 두면 파드 재시작 시 **실험 기록이 통째로 사라진다.**
|
||||
사후 추적이 목적이면 PVC여야 한다.
|
||||
|
||||
### 관측 시스템의 장애 도메인
|
||||
|
||||
**관측 시스템은 관측 대상과 같이 죽으면 안 된다.** 죽는 순간을 기록해야
|
||||
하는데 같이 죽으면 기록이 없다.
|
||||
|
||||
노드가 둘뿐인 실험대에서는 완전히 피할 수 없으므로 **규칙으로 정한다.**
|
||||
|
||||
```
|
||||
kc-lab-1 (server) 관측 스택을 둔다. 죽이지 않는다
|
||||
kc-lab-2 (agent) 장애 주입 대상
|
||||
```
|
||||
|
||||
`nodeSelector`로 못박아 실험이 재현 가능하게 만든다.
|
||||
|
||||
---
|
||||
|
||||
## 13층. 가상화 운영 — 실행 중 바꾸는 것들
|
||||
|
||||
### VM 메모리 재배분 — 게스트를 다시 만들지 않는다
|
||||
|
||||
```bash
|
||||
virsh setmaxmem kc-lab-1 5120M --config
|
||||
virsh setmem kc-lab-1 5120M --config
|
||||
```
|
||||
|
||||
| 명령 | 바꾸는 것 |
|
||||
|---|---|
|
||||
| `setmaxmem` | **상한**. 부팅 시 게스트가 보는 총량 |
|
||||
| `setmem` | **현재 할당**. 상한 이하여야 한다 |
|
||||
|
||||
**순서가 중요하다.** 현재값을 상한보다 크게 줄 수 없으므로 `setmaxmem`이
|
||||
먼저다.
|
||||
|
||||
| 플래그 | 적용 범위 |
|
||||
|---|---|
|
||||
| `--config` | 영구 정의. **다음 부팅부터** |
|
||||
| `--live` | 실행 중인 도메인에 즉시 |
|
||||
| 둘 다 | 지금과 앞으로 |
|
||||
|
||||
`setmaxmem --live`는 대개 거부된다 — 게스트가 부팅 시 메모리 맵을 정하기
|
||||
때문이다. **상한을 바꾸려면 게스트를 껐다 켜야 한다.**
|
||||
|
||||
```bash
|
||||
virsh dominfo kc-lab-1 | grep -i memory
|
||||
ssh kc-lab-1 free -m # 게스트가 실제로 인식한 값
|
||||
```
|
||||
|
||||
호스트에서 8GB→12GB로 물리 증설한 뒤 이 방법으로 재배분했다.
|
||||
**게스트 재생성이나 디스크 조작은 전혀 필요 없었다.**
|
||||
|
||||
### 안전한 종료 순서
|
||||
|
||||
전원을 내리기 전에 **위에서부터** 정리한다.
|
||||
|
||||
```bash
|
||||
# 1. 애플리케이션 — 클러스터에서 정상 탈퇴
|
||||
kubectl -n keycloak-lab scale statefulset/keycloak --replicas=0
|
||||
kubectl -n keycloak-lab wait --for=delete pod -l app=keycloak --timeout=120s
|
||||
|
||||
# 2. 데이터베이스 — 마지막에, 충분한 시간을 주고
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
||||
kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=120s
|
||||
|
||||
# 3. 게스트 — ACPI 정상 종료
|
||||
virsh shutdown kc-lab-1 && virsh shutdown kc-lab-2
|
||||
|
||||
# 4. 호스트
|
||||
sudo systemctl poweroff
|
||||
```
|
||||
|
||||
**왜 순서가 중요한가** — `virsh shutdown`은 게스트 systemd가 k3s를 멈추고,
|
||||
k3s가 컨테이너에 SIGTERM을 보낸다. 유예 시간이 짧으면 **PostgreSQL이
|
||||
강제 종료되어 다음 기동에 crash recovery가 돈다.** 미리 내려두면 그 위험이
|
||||
없다.
|
||||
|
||||
**clean shutdown 확인**
|
||||
|
||||
```bash
|
||||
ssh kc-lab-2 'sudo ls /var/lib/rancher/k3s/storage/*postgres-data*/pgdata/postmaster.pid'
|
||||
```
|
||||
|
||||
**`postmaster.pid`가 남아 있지 않아야 정상**이다. 남아 있으면 비정상 종료였고
|
||||
다음 기동에 복구 절차가 실행된다.
|
||||
|
||||
### 복구 순서 — 종료의 역순
|
||||
|
||||
```bash
|
||||
virsh start kc-lab-1 && virsh start kc-lab-2
|
||||
kubectl get nodes # Ready 2개 대기
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/postgres
|
||||
kubectl -n keycloak-lab scale statefulset/keycloak --replicas=2
|
||||
```
|
||||
|
||||
**PostgreSQL이 먼저다.** Keycloak이 DB 없이 뜨면 기동에 실패한다.
|
||||
|
||||
**스케일을 0으로 내려두면 자동으로 복구되지 않는다.** 명시적으로 올려야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 아직 기록하지 않은 개념
|
||||
|
||||
실험 설계 단계에서 아래 항목을 이 문서에 추가한다.
|
||||
실험을 진행하면서 이 문서에 추가한다.
|
||||
|
||||
- Infinispan, `DIST_SYNC`, `numOwners`, 캐시별 설정
|
||||
- JGroups, `JDBC_PING`, 디스커버리와 트랜스포트의 분리, TCP 7800
|
||||
- `persistent-user-sessions` / `volatile-user-sessions`
|
||||
- 원격 Infinispan(Hot Rod)과 multi-site
|
||||
- refresh token rotation, revoke, max reuse, 동시 갱신 경쟁
|
||||
- `persistent-user-sessions` / `volatile-user-sessions` 의 실제 차이 (로드맵 2번)
|
||||
- refresh token rotation·revoke·max reuse 와 동시 갱신 경쟁 (로드맵 5번)
|
||||
- SSO 세션 vs 애플리케이션 세션, `KEYCLOAK_IDENTITY`, `AUTH_SESSION_ID`
|
||||
- 백채널 로그아웃과 `sid` 역인덱스
|
||||
- 쿠키 `Secure` / `SameSite` / `HttpOnly`
|
||||
- Redis 영속화(RDB/AOF)와 세션 복구
|
||||
- `tc netem`, OOM killer와 `oom_score`, fsync와 페이지 캐시
|
||||
- Spring Session / `OAuth2AuthorizedClientService` 의 저장 구조
|
||||
- `tc netem` 지연 주입
|
||||
- OOM killer 와 `oom_score`
|
||||
- fsync 와 페이지 캐시, EBS IOPS
|
||||
|
||||
### 이번에 채운 것 (2026-09-04)
|
||||
|
||||
10~13층으로 기록 완료 — StatefulSet·DaemonSet, PVC/PV/StorageClass,
|
||||
Secret, RBAC 와 서브리소스, nodeSelector·taint, k3s server/agent 차이,
|
||||
Infinispan·JGroups(디스커버리 vs 트랜스포트, GMS/FD_SOCK2/MERGE3),
|
||||
Prometheus(pull·SD·relabel·메트릭 타입·`up`·TSDB), VM 메모리 재배분,
|
||||
안전한 종료·복구 순서.
|
||||
|
||||
Reference in New Issue
Block a user