4646from rich .table import Table
4747from rich .text import Text
4848
49+ try :
50+ from watchdog .events import FileSystemEventHandler
51+ from watchdog .observers import Observer
52+ except ImportError :
53+ Observer = None # type: ignore
54+ FileSystemEventHandler = None # type: ignore
55+
4956try :
5057 from urllib3 .exceptions import ConnectTimeoutError , MaxRetryError , ReadTimeoutError
5158except ImportError :
5259 ConnectTimeoutError = MaxRetryError = ReadTimeoutError = Exception # type: ignore
5360
5461console = Console ()
5562
63+ # 컨텍스트 변경 감지를 위한 전역 플래그
64+ CONTEXT_CONFIG_NEEDS_RELOAD = False
65+
66+
67+ # Kubeconfig 변경을 감지하는 핸들러
68+ if FileSystemEventHandler :
69+
70+ class KubeConfigChangeHandler (FileSystemEventHandler ):
71+ """Kubeconfig 파일 변경을 감지하여 플래그를 설정."""
72+
73+ def __init__ (self , file_path : Path ):
74+ self .file_path = file_path
75+
76+ def on_modified (self , event : Any ) -> None :
77+ if not event .is_directory and Path (event .src_path ) == self .file_path :
78+ global CONTEXT_CONFIG_NEEDS_RELOAD
79+ CONTEXT_CONFIG_NEEDS_RELOAD = True
80+ console .print (
81+ "\n [bold yellow]Kubeconfig 변경 감지. 다음 갱신 시 컨텍스트를 다시 로드합니다.[/bold yellow]"
82+ )
83+
84+
85+ def start_kube_config_watcher () -> None :
86+ """Kubeconfig 파일 감시자를 백그라운드 스레드에서 시작."""
87+ if not Observer :
88+ return
89+
90+ kube_config_path = Path (os .path .expanduser ("~/.kube/config" ))
91+ if not kube_config_path .is_file ():
92+ return
93+
94+ event_handler = KubeConfigChangeHandler (kube_config_path )
95+ observer = Observer ()
96+ observer .schedule (event_handler , str (kube_config_path .parent ), recursive = False )
97+ observer .daemon = True
98+ observer .start ()
99+
100+
101+
56102# 노드그룹 라벨을 변수로 분리 (기본값: node.kubernetes.io/app)
57103NODE_GROUP_LABEL = "node.kubernetes.io/app"
58104
@@ -867,21 +913,28 @@ async def _graceful_shutdown(timeout: float = 10.0) -> None:
867913 globals ()["_async_graceful_shutdown" ] = _graceful_shutdown # for advanced usage
868914
869915
870- def load_kube_config () -> None :
871- """kube config 로드 (예외처리 포함)"""
916+ def reload_kube_config_if_changed (force : bool = False ) -> bool :
917+ """kube config 변경이 감지되었거나 강제 실행 시 리로드 후 True 반환."""
918+ global CONTEXT_CONFIG_NEEDS_RELOAD
919+ if not force and not CONTEXT_CONFIG_NEEDS_RELOAD :
920+ return False
921+
872922 try :
923+ config .kube_config .KubeConfigLoader .cleanup_and_reset ()
873924 config .load_kube_config ()
925+ CONTEXT_CONFIG_NEEDS_RELOAD = False
926+ console .print ("\n [bold green]Kubeconfig를 다시 로드했습니다.[/bold green]" )
927+ return True
874928 except Exception as e :
875- print (f"Error loading kube config : { e } " )
876- sys . exit ( 1 )
929+ console . print (f"\n [bold red]Kubeconfig 리로드 실패 : { e } [/bold red] " )
930+ return False
877931
878932
879933def choose_namespace () -> Optional [str ]:
880934 """
881935 클러스터의 모든 namespace 목록을 표시하고, 사용자가 index로 선택
882936 아무 입력도 없으면 전체(namespace 전체) 조회
883937 """
884- load_kube_config ()
885938 v1 = client .CoreV1Api ()
886939 try :
887940 ns_list : V1NamespaceList = v1 .list_namespace (
@@ -947,7 +1000,6 @@ def choose_node_group() -> Optional[str]:
9471000 클러스터의 모든 노드 그룹 목록(NODE_GROUP_LABEL로부터) 표시 후, 사용자가 index로 선택
9481001 아무 입력도 없으면 필터링하지 않음
9491002 """
950- load_kube_config ()
9511003 v1 = client .CoreV1Api ()
9521004 try :
9531005 nodes = v1 .list_node ().items
@@ -1059,7 +1111,6 @@ def watch_event_monitoring() -> None:
10591111 tail_num_raw = get_tail_lines ("몇 줄씩 확인할까요? (예: 20): " )
10601112 tail_limit = _parse_tail_count (tail_num_raw )
10611113
1062- load_kube_config ()
10631114 v1 = client .CoreV1Api ()
10641115 field_selector = "type!=Normal" if event_choice == "2" else None
10651116 if ns :
@@ -1075,6 +1126,9 @@ def watch_event_monitoring() -> None:
10751126 with Live (console = console , auto_refresh = False ) as live :
10761127 tracker = LiveFrameTracker (live )
10771128 while True :
1129+ if reload_kube_config_if_changed ():
1130+ v1 = client .CoreV1Api ()
1131+
10781132 try :
10791133 if ns :
10801134 response = v1 .list_namespaced_event (
@@ -1296,7 +1350,7 @@ def view_restarted_container_logs() -> None:
12961350 최근 재시작된 컨테이너 목록에서 선택하여 이전 컨테이너의 로그 확인
12971351 """
12981352 console .print ("\n [2] 재시작된 컨테이너 확인 및 로그 조회" , style = "bold blue" )
1299- load_kube_config ()
1353+ reload_kube_config_if_changed ()
13001354 v1 = client .CoreV1Api ()
13011355 ns = choose_namespace ()
13021356 pods = get_pods (v1 , ns )
@@ -1379,7 +1433,6 @@ def watch_pod_monitoring_by_creation() -> None:
13791433 tail_num_raw = get_tail_lines ("몇 줄씩 확인할까요? (예: 20): " )
13801434 tail_limit = _parse_tail_count (tail_num_raw )
13811435
1382- load_kube_config ()
13831436 v1 = client .CoreV1Api ()
13841437 if ns :
13851438 command_descriptor = (
@@ -1397,6 +1450,8 @@ def watch_pod_monitoring_by_creation() -> None:
13971450 with Live (console = console , auto_refresh = False ) as live :
13981451 tracker = LiveFrameTracker (live )
13991452 while True :
1453+ if reload_kube_config_if_changed ():
1454+ v1 = client .CoreV1Api ()
14001455 try :
14011456 if ns :
14021457 response = v1 .list_namespaced_pod (
@@ -1665,7 +1720,6 @@ def watch_non_running_pod() -> None:
16651720 tail_num_raw = get_tail_lines ("몇 줄씩 확인할까요? (예: 20): " )
16661721 tail_limit = _parse_tail_count (tail_num_raw )
16671722
1668- load_kube_config ()
16691723 v1 = client .CoreV1Api ()
16701724 if ns :
16711725 command_descriptor = (
@@ -1682,6 +1736,8 @@ def watch_non_running_pod() -> None:
16821736 with Live (console = console , auto_refresh = False ) as live :
16831737 tracker = LiveFrameTracker (live )
16841738 while True :
1739+ if reload_kube_config_if_changed ():
1740+ v1 = client .CoreV1Api ()
16851741 try :
16861742 if ns :
16871743 response = v1 .list_namespaced_pod (
@@ -1936,13 +1992,14 @@ def watch_pod_counts() -> None:
19361992 )
19371993 ns = choose_namespace ()
19381994 console .print ("\n (Ctrl+C로 중지 후 메뉴로 돌아갑니다.)" , style = "bold yellow" )
1939- load_kube_config ()
19401995 v1 = client .CoreV1Api ()
19411996 try :
19421997 with suppress_terminal_echo ():
19431998 with Live (console = console , auto_refresh = False ) as live :
19441999 tracker = LiveFrameTracker (live )
19452000 while True :
2001+ if reload_kube_config_if_changed ():
2002+ v1 = client .CoreV1Api ()
19462003 pods = get_pods (v1 , ns )
19472004 total = len (pods )
19482005 normal = sum (
@@ -2018,7 +2075,6 @@ def watch_node_monitoring_by_creation() -> None:
20182075 tail_num_raw = get_tail_lines ("몇 줄씩 확인할까요? (예: 20): " )
20192076 tail_limit = _parse_tail_count (tail_num_raw )
20202077
2021- load_kube_config ()
20222078 v1 = client .CoreV1Api ()
20232079 command_descriptor = (
20242080 "Python client: CoreV1Api.list_node (sorted by creationTimestamp)"
@@ -2034,6 +2090,8 @@ def watch_node_monitoring_by_creation() -> None:
20342090 with Live (console = console , auto_refresh = False ) as live :
20352091 tracker = LiveFrameTracker (live )
20362092 while True :
2093+ if reload_kube_config_if_changed ():
2094+ v1 = client .CoreV1Api ()
20372095 try :
20382096 response = v1 .list_node (_request_timeout = API_REQUEST_TIMEOUT )
20392097 nodes = list (getattr (response , "items" , []) or [])
@@ -2260,7 +2318,6 @@ def watch_unhealthy_nodes() -> None:
22602318 tail_num_raw = get_tail_lines ("몇 줄씩 확인할까요? (예: 20): " )
22612319 tail_limit = _parse_tail_count (tail_num_raw )
22622320
2263- load_kube_config ()
22642321 v1 = client .CoreV1Api ()
22652322 command_descriptor = "Python client: CoreV1Api.list_node (exclude Ready)"
22662323 if filter_nodegroup :
@@ -2274,6 +2331,8 @@ def watch_unhealthy_nodes() -> None:
22742331 with Live (console = console , auto_refresh = False ) as live :
22752332 tracker = LiveFrameTracker (live )
22762333 while True :
2334+ if reload_kube_config_if_changed ():
2335+ v1 = client .CoreV1Api ()
22772336 try :
22782337 response = v1 .list_node (_request_timeout = API_REQUEST_TIMEOUT )
22792338 nodes = list (getattr (response , "items" , []) or [])
@@ -2651,7 +2710,6 @@ def watch_pod_resources() -> None:
26512710 if filter_choice .startswith ("y" ):
26522711 filter_nodegroup = choose_node_group () or ""
26532712
2654- load_kube_config ()
26552713 v1 = client .CoreV1Api ()
26562714 node_filter : Optional [Set [str ]] = None
26572715 if filter_nodegroup :
@@ -2669,6 +2727,16 @@ def watch_pod_resources() -> None:
26692727 with Live (console = console , auto_refresh = False ) as live :
26702728 tracker = LiveFrameTracker (live )
26712729 while True :
2730+ if reload_kube_config_if_changed ():
2731+ v1 = client .CoreV1Api ()
2732+ if filter_nodegroup :
2733+ node_filter = _collect_nodes_for_group (v1 , filter_nodegroup )
2734+ if not node_filter :
2735+ console .print (
2736+ "[bold red]NodeGroup 필터에 해당하는 노드를 찾을 수 없습니다. 필터를 리셋합니다.[/bold red]"
2737+ )
2738+ filter_nodegroup = "" # Reset filter
2739+
26722740 metrics , error , kubectl_cmd = _get_kubectl_top_pod (namespace )
26732741 if error :
26742742 frame_key = _make_frame_key ("error" , error )
@@ -2896,6 +2964,8 @@ def main() -> None:
28962964 """
28972965 메인 함수 실행
28982966 """
2967+ start_kube_config_watcher ()
2968+ reload_kube_config_if_changed (force = True ) # 초기 강제 로드
28992969 try :
29002970 while True :
29012971 choice = main_menu ()
0 commit comments