황현석 일지

클라우드 서버 구축 트러블 슈팅 - 01 K3s API Server Connection 본문

Kubernates

클라우드 서버 구축 트러블 슈팅 - 01 K3s API Server Connection

황현석 2026. 1. 14. 05:04

 

클라우드 서비스를 구축하면서, 클라우드 서비스를 제어할 백엔드를 구축하기 위해 많은 삽질을 했다. Kubernetes를 기본적으로 사용 할 것이고, K3s라는 가벼운 K8s 배포판을 사용하여 단일 노드 환경에서 컨테이너들을 오케스트레이션할 것이다.

 

백엔드는 VM안에서 Docker없이 간단하게 돌아가면서, 빠른 속도, K8s와의 좋은 생태계, 멀티코어를 기반으로 한 서버를 고려하여, Go랭을 채택하였다.

 

1. Masquerade: VM 네트워크의 이중 캡슐화 해결

클라우드 서비스는 기본적으로 Volume Container와 이를 영구 저장할 Persistence Volume(PV)을 제공해야 한다. 이를 구현하기 위해 KubeVirt 오픈소스를 사용하여 가상 머신(VM)을 커스텀 리소스로 정의했다. KubeVirt를 통해 VM에 쓰일 파일들을 YAML로 미리 구성했다.

 

아키텍처 구상 중, 물리 서버와 K3s가 별도로 도는 게 비효율적이라 판단하여 백엔드 제어 서비스마저 K3s 내부의 VM(혹은 컨테이너)에서 작동하도록 구성했다. 이식성은 좋아졌지만, VM 내에서 K3s API 서버와 연결하는 과정에서 수많은 네트워크 트러블슈팅이 발생했다.

 

KubeVirt를 사용하면 네트워크가 두 번 감싸진다고 보면 된다. 트래픽이 VM을 빠져나오고, Pod를 거쳐, Node로 나가는 구조다. 여기서 Masquerade 모드를 활성화하지 않으면, VM 내부 트래픽이 Kube-proxy의 적용을 받지 못해 내부 Cluster-IP로의 프록싱이 깨진다.

 

Masquerade를 사용하면 VM 내부 요청의 Source IP가 Pod IP로 NAT(Network Address Translation) 되어 고정된다. 덕분에 패킷이 Kube-proxy를 정상적으로 통과하여 다른 Pod나 Cluster-IP 대역으로 도달할 수 있게 된다. 따라서 VM 내부에서 K3s를 제어하기 위한 최소한의 통신 조건으로 Masquerade 설정 해야 한다.

 

2. Network Policy에 의한 API Server Connection Refused

Masquerade 설정 후에도 192.168.0.21:6443(외부 IP)이나 Kube-proxy를 통한 API 서버 연결이 계속 실패했다. 원인은 허무하게도 내가 설정해둔 강력한 Network Policy 때문이었다. 보안을 위해 Egress를 10.0.0.0/8 대역 외에는 전부 차단했는데, K3s API 서버의 기본 서비스 대역인 10.43.0.1이 이 정책에 걸려 막혔다.

 

더 큰 문제는 정책 삭제 과정에서 발생했다. Namespace를 명시하지 않고 kubectl 명령어로 정책을 수정/삭제하다 보니, Default 네임스페이스와 Cloud 네임스페이스 양쪽에 적용되어 있던 정책 중 하나만 처리된 것을 보고 다 해결된 줄 착각했다. 특수한 리소스를 정의할 때는 YAML 파일 내부에 반드시 Namespace를 명시해야한다.

 

이 두 가지 문제를 해결하고 나서야 비로소 단일 노드 K3s 내부의 KubeVirt VM 안에서 API 서버로의 경로가 모두 뚫리는 것을 확인할 수 있었다.

 

3. K3s Secrets Load: ServiceAccount 주입과 마운트 이슈

K3s 제어는 HTTPS 통신을 통한 REST API로 이루어진다. 이를 위해 VM 내부에 인증서, 권한 토큰, ServiceAccount 정보가 로드되어야 한다. K8s의 RBAC(Role-Based Access Control)에 따라 Account와 Role을 만들고 Binding 한 뒤, 이를 VM의 볼륨으로 주입했다.

 

- name: sa-token 
	serial: "k8s-token" # /dev/disk/by-id/virtio-k8s-token으로 접근 가능 
	disk: { bus: virtio }

volumes:
	name: sa-token 
    serviceAccount: 
    	serviceAccountName: cloud-account

 

여기서 발생한 트러블은 Virtio 디스크 블록 장치로만 인식되고, Guest OS 내의 파일시스템에 자동으로 마운트되지 않는다는 점이었다. 쉽게 말해 장치는 연결됐는데 OS에서 쓸 수 있게 마운트가 안 된 상태다. 이를 해결하기 위해 cloud-initruncmd를 활용하여 부팅 시 해당 블록 장치를 특정 경로에 마운트하는 명령어를 추가하여 해결했다.

 

하지만, Virtctl restart등의, VM 재시작시, 런타임 내에 해당 증명서들이 로드 되지 않아서, 좀 뒤져보면서 찾아보다가 그냥, bootcmd에다가 넣어서 적용했다.

 

 

4. VM Restart 시의 네트워크 레이스 컨디션 (systemd-networkd)

마지막 이슈는 virtctl로 VM을 재시작할 때 간헐적으로 네트워크 설정이 Failed 되는 현상이었다. 로그를 분석하니 systemd-networkd-wait-online.service에서 타임아웃이 발생하고 있었다.

 

이는 일종의 동시성(Race Condition) 이슈로, 가상 브릿지가 준비되기 전에 Guest OS의 네트워크 서비스가 먼저 실행되어 DHCP 응답을 제때 받지 못해 발생하는 문제였다. VM 환경이 특수하거나 호스트 리소스 경합이 있을 때 자주 발생한다. 임시로 부팅 후 최초 실행 스크립트에서 네트워크 인터페이스를 재설정하도록 구성하여 해결했다.

 

처음 아키텍처를 구성할 때는 VM 내부 IP 할당 실패로 인해 Ingress까지 먹통이 되어 원인 파악이 힘들었지만, 결국 모든 계층(L4 NodePort, iptables, VM 내부 상태)을 하나씩 검증하며 해결할 수 있었다. 비록 보안상 완벽한 멀티테넌트 격리는 더 세밀한 YAML 권한 제어가 필요하겠지만, 현재의 구조는 관리용 VM으로서 충분한 권한과 안정성을 확보했다.

 

 

이와 같은 트러블 슈팅 후, VM내부의 Backend에서 K3s Master Node Cluster에 잘 연결 된 것을 볼 수 있었다.

'Kubernates' 카테고리의 다른 글

Kubernetes - 01  (2) 2025.07.19