κ°μ₯ κΈ΄μ₯νλ μκ°μ΄μμ΅λλ€. Worker 2μ λμ μ μ§μ μμΌλ‘ λ½λ κ·Έ μκ°. μ무 λͺ λ Ήλ μ λ ₯νμ§ μμ μ±λ‘ νλ©΄μ λ°λΌλ³΄λ©° κΈ°λ€λ Έμ΅λλ€. 30μ΄κ° μ§λκ³ , 1λΆμ΄ μ§λκ³ . κ·Έλ¦¬κ³ Slackμ μλμ΄ μμ΅λλ€. νλκ° Worker 1μΌλ‘ μ΄λνκ³ , κ°μκ° μ¬κ°λμ΅λλ€. μ΄ ν¬μ€ν μ κ·Έ μ₯λ©΄μ λ§λ€κΈ° μν μ€κ³μ μλνμ κΈ°λ‘μ λλ€.
π Failover μ€μ¦: GitHub Wiki β Proof Failover | μλ νμ΄νλΌμΈ | Ansible IaC
ποΈ Failoverκ° λμνκΈ° μν 4κ°μ λ§λ¬Όλ¦Ό
Failoverλ λ¨μΌ κΈ°λ₯μ΄ μλλλ€. λ€ κ°μ§ μ€κ³κ° μ νν λ§λ¬Όλ €μΌ ν©λλ€.
| μ€κ³ μμ | μν | ν΅μ¬ μ€μ |
|---|---|---|
| nodeAffinity | AI νλλ₯Ό νμμ Worker 2 μ°μ λ°°μΉ | preferredDuringScheduling |
| tolerationSeconds | λ Έλ λ€μ΄ κ°μ§ ν λκΈ° μκ° μ€μ | tolerationSeconds: 30 |
| Longhorn HA | 3-Node μ€ν λ¦¬μ§ λ³΅μ λ‘ λ°μ΄ν° 보쑴 | 3-way replication |
| auto_failback.sh | Worker 2 볡ꡬ μ νλ μλ μ볡 | λ§€λΆ λ Έλ μν κ°μ |
nodeAffinity β νμμ λΉμμ λ°°μΉ μ λ΅
spec:
affinity:
nodeAffinity:
# νμ: Worker 2 μ°μ λ°°μΉ (Danger Zoneμμ μ§μ κ°μ§)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: zone
operator: In
values:
- danger
# λΉμμ: Worker 1 μ΄λ νμ© (Buffer ZoneμΌλ‘ μ무 μΉκ³)
tolerations:
- key: "zone"
operator: "Equal"
value: "buffer"
effect: "NoSchedule"
tolerationSeconds: 30
tolerationSeconds β 30μ΄μ μλ―Έ
μ²μμ ArgoCDμ κΈ°λ³Έ Sync μ£ΌκΈ°μΈ 5λΆμ μ¬μ©νμ΅λλ€. κ·Έλ°λ° μ€μ μ¬κ³ μν©μμ 5λΆμ κ³΅λ°±μ΄ μΌλ§λ μΉλͺ
μ μΈμ§λ₯Ό μκ°νκ³ tolerationSeconds: 30μΌλ‘ νλνμ΅λλ€. λ¨μν μ«μ λ³κ²½μ΄μ§λ§ μ΄ κ²°μ λ€μλ “μ΄ μμ€ν
μ΄ μ€μ λ‘ νμ₯μμ μ°μΈλ€λ©΄” μ΄λΌλ μ§λ¬Έμ΄ μμμ΅λλ€.
β±οΈ Failover 6λ¨κ³ νμλΌμΈ
T + 0s βββ Worker2 λμ κ°μ μ κ±°
βββΊ K3s heartbeat μμ€ κ°μ§ μμ
T + 30s βββ tolerationSeconds λ§λ£
βββΊ AI νλ Evict νΈλ¦¬κ±° λ°λ
T + 60s βββ Grafana Alert 쑰건 μΆ©μ‘± (1λΆ μ§μ)
βββΊ Alert Firing μν μ ν
T + 90s βββ Slack 1λ¨κ³ μλ π¨
βββΊ "[κΈ΄κΈ] worker2 λ
Έλ λ€μ΄ κ°μ§"
T + 3~5m βββ AI νλ Worker1 μ¬μ€μΌμ€λ§ μλ£
βββΊ νμ¬Β·μμΈΒ·μμ κ°μ μ¬κ°
T + 5~6m βββ Slack 2λ¨κ³ μλ π
βββΊ "[μλ£] Failover μ±κ³΅ β worker1 μΉκ³"
T + 볡ꡬ βββ Worker2 λμ μ¬μ°κ²°
βββΊ auto_failback.sh μλ κ°μ§
βββΊ νλ Worker2 μ볡
βββΊ Slack 3λ¨κ³ μλ β
"[RESOLVED] worker2 μ μ 볡ꡬ"
π μ€μ¦ κ²°κ³Ό
Failover μ β AI νλκ° Worker2μμ μ€ν μ€
Failover ν β AI νλκ° Worker1μΌλ‘ μλ μ΄λ
Worker2 NotReady μν νμΈ
| κ²μ¦ νλͺ© | λͺ©ν | κ²°κ³Ό |
|---|---|---|
| λ Έλ λ€μ΄ κ°μ§ μκ° | 30μ΄ μ΄λ΄ | β |
| AI νλ μ΄λ μκ° | 3~5λΆ μ΄λ΄ | β |
| λ°μ΄ν° μ μ€ | 0건 | β |
| Slack μλ μλ λ°μ‘ | 3λ¨κ³ μ λΆ | β |
| Failback μλ μ볡 | Worker2 볡ꡬ μ μλ | β |
π¨ λ¬΄μΈ 3λ¨κ³ Slack μλ νμ΄νλΌμΈ
μ¬λμ΄ μ무κ²λ νμ§ μμλ μμ€ν μ΄ μ€μ€λ‘ μνλ₯Ό μΆμ νκ³ λ³΄κ³ ν©λλ€.
Prometheus (Worker1 NodePort 30090)
β λ©νΈλ¦ μμ§
βΌ
Grafana Alert Rules
β 쑰건 μΆ©μ‘± μ Webhook λ°μ‘
βΌ
Slack Webhook
βββΊ 1λ¨κ³ π¨ λ
Έλ λ€μ΄ κ°μ§
βββΊ 2λ¨κ³ π Failover μΉκ³ μλ£
βββΊ 3λ¨κ³ β
λ
Έλ μ μ 볡ꡬ
1λ¨κ³ π¨ β λ Έλ λ€μ΄ κ°μ§ (T+90s)
kube_node_status_condition{node="worker2", condition="Ready", status="true"} < 1
Worker2μ Ready μνκ° 1μμ λ²μ΄λλ©΄ 1λΆ μ§μ ν FiringμΌλ‘ μ νλ©λλ€. μκ°μ μΈ λ€νΈμν¬ μ§μ°κ³Ό ꡬλΆνκΈ° μν 1λΆ μ μμ
λλ€.
2λ¨κ³ π β Failover μΉκ³ μλ£ (T+5~6m)
count(kube_pod_info{node="worker1", namespace="ai-apps"}) > 0
AI νλκ° Worker1μμ μ€νλκΈ° μμνλ©΄ 1λΆ μ§μ ν FiringμΌλ‘ μ νλ©λλ€.
3λ¨κ³ β β λ Έλ μ μ 볡ꡬ (Worker2 볡ꡬ μ)
1λ¨κ³ μλμ Send resolved μ΅μ
λ§ νμ±ννλ©΄ λ©λλ€. λ³λ 쿼리 μμ΄, 1λ¨κ³ μ‘°κ±΄μ΄ ν΄μλλ©΄ Grafanaκ° μλμΌλ‘ RESOLVEDλ₯Ό λ°μ‘ν©λλ€.
Range νμ μ μ¨μΌ νλ μ΄μ
Grafana Alert 쿼리 νμ
μλ Instantμ Range λ κ°μ§κ° μμ΅λλ€. Instantλ νμ¬ μμ κ°λ§ μ‘°ννλλ°, Prometheus μ€ν¬λ μ΄ν μ£ΌκΈ°μ Grafana νκ° μ£ΌκΈ°κ° μ΄κΈλλ©΄ λ©νΈλ¦μ΄ nullλ‘ λ°νλμ΄ μλμ΄ λ°μ‘λμ§ μκ±°λ μ€λ°μ‘λ©λλ€. Rangeλ₯Ό μ¬μ©νκ³ Replace Non-numeric with 0 μ΅μ
μ μΌλ©΄ nullμ 0μΌλ‘ λ체νμ¬ νμ μμ μ μΌλ‘ 쑰건μ νκ°ν©λλ€.
βοΈ Ansible IaC β 3-Node μΌκ΄ μ΄μ μλν
νλ‘μ νΈ μ΄λ°μλ λ Έλμ μ§μ SSHλ‘ μ μν΄μ μ€μ νμ΅λλ€. μ΄λμλ κΈ°λ‘μ΄ λ¨μ§ μμΌλ©΄ μ¬νμ΄ μ λλ€λ κ±Έ μ€κ°μ κΉ¨λ«κ³ Ansibleλ‘ μ λΆ μ½λννμ΅λλ€.
| Playbook | μν |
|---|---|
network.yml | Jinja2 ν νλ¦ΏμΌλ‘ Netplan κ³ μ IP μ λ Έλ λ°°ν¬ |
nfs.yml | NFS λ§μ΄νΈ μλν (state: ephemeral β λΆν
μ§μ° λ°©μ§) |
tiering.yml | μ€ν¬λ¦½νΈ 3κ° λ°°ν¬ + crontab 3κ° λ±λ‘ (λ©±λ±μ± 보μ₯) |
healthcheck.yml | K3s μλΉμ€Β·νλΒ·NFS μν νμΈ + Slack μλ μλ¦Ό |
ν΅μ¬: crontab λ©±λ±μ± μ²λ¦¬
- name: Register tiering crontab
ansible.builtin.cron:
name: "run_tiering" # μ΄λ¦μΌλ‘ μ€λ³΅ μλ³
minute: "0"
hour: "*"
job: "/home/minsoo/scripts/run_tiering.sh >> /var/log/tiering.log 2>&1 #Ansible"
state: present
name νλλ‘ λμΌν crontabμ μλ³νλ―λ‘, Playbookμ μ¬λ¬ λ² μ€νν΄λ crontabμ΄ μ€λ³΅ λ±λ‘λμ§ μμ΅λλ€.
NFSλ₯Ό state: ephemeralλ‘ λ§μ΄νΈνλ μ΄μ
state: mounted(fstab λ±λ‘)λ₯Ό μ°λ©΄ Windows PC(WinNFSd)κ° κΊΌμ§ μνμμ λΌμ¦λ² 리νμ΄κ° λΆν
λ λ NFS λ§μ΄νΈλ₯Ό κΈ°λ€λ¦¬λ€κ° λΆν
μ΄ μλΆ μ΄μ μ§μ°λ©λλ€. ephemeralμ fstabμ λ±λ‘νμ§ μμ λΆν
μ§μ° μμ΄ μ΄μ μ€μλ§ λ§μ΄νΈν©λλ€.
β μμ€ν μ 체 κ²μ¦ κ²°κ³Ό
| λͺ©ν | κ²°κ³Ό |
|---|---|
| RPO 0μ΄ (λ°μ΄ν° 무μμ€) | β Longhorn 3-Node 볡μ λ‘ λ¬μ± |
| RTO 2λΆ μ΄λ΄ | β ~5λΆ (νλ μ΄λ ν¬ν¨, λͺ©νμΉ λλΉ νμ© λ²μ λ΄) |
| λ¬΄μΈ μλ Failover | β μ¬λ κ°μ μμ΄ μμ μλ |
| λ¬΄μΈ μλ Failback | β auto_failback.sh λ§€λΆ κ°μ |
| 3λ¨κ³ Slack μλ | β μ λ¨κ³ μλ λ°μ‘ |
| Ansible IaC | β 4κ° PlaybookμΌλ‘ μ μΈνλΌ μ½λν |
π λ€μ νΈ: 7νΈ β νκ³ : λΌμ¦λ² 리νμ΄ 3λλ‘ μν°νλΌμ΄μ¦ HAλ₯Ό ꡬννκ³ λμ
π Ansible μ€μ¦ μμΈ: GitHub Wiki β Proof Ansible