본문 바로가기
IT/k8s

[홈랩 K8s #2] Talos Linux에 Cilium CNI 올리기 — Flannel 교체부터 kube-proxy 제거, Hubble 관측성까지

by 수누다 2026. 4. 3.

들어가며: 예고편 회수하러 왔습니다

지난 글에서 Talos Linux를 Proxmox 홈랩에 올리는 이야기를 하면서 말미에 이렇게 적었습니다. "다음 편은 Talos 위에 올린 Cilium CNI 설정기로 돌아오겠습니다." 약속을 지키러 왔습니다.

Talos를 설치하고 kubectl get nodes에서 Ready가 뜨는 순간은 짜릿했지만, 곧 현실적인 질문이 따라옵니다. "기본으로 깔려 있는 Flannel... 이대로 써도 되는 건가?" 결론부터 말하면, 홈랩이든 프로덕션이든 2026년에 Flannel을 쓸 이유는 거의 없습니다. 오늘은 그 이유와 함께, Talos에서 Cilium을 올리고 kube-proxy까지 걷어내는 전체 과정을 처음부터 끝까지 다룹니다.


1. 환경 정보

본 가이드의 테스트 환경입니다. 버전이 다르면 설정값이 달라질 수 있으니 반드시 확인하세요.

항목 버전/스펙
Hypervisor Proxmox VE 8.x
Talos Linux v1.9.x
Kubernetes v1.32.x
Cilium v1.19.x (Helm chart)
Cilium CLI v0.18.x
노드 구성 Control Plane ×1, Worker ×2
CNI (교체 전) Flannel (Talos 기본)
CNI (교체 후) Cilium (eBPF 모드, kube-proxy 대체)

💡 Talos는 KubePrism이라는 내장 로드밸런서를 통해 localhost:7445로 Kubernetes API에 접근합니다. 이 포트 번호는 Cilium 설정에서 중요하게 쓰이니 기억해 두세요.


2. 왜 Flannel을 버리고 Cilium인가?

iptables의 한계: O(n)의 저주

전통적인 kube-proxy는 iptables 규칙으로 서비스 로드밸런싱을 처리합니다. 서비스 10개일 때는 문제가 없지만, 서비스 1,000개에 엔드포인트 5,000개가 되면 수만 개의 iptables 규칙이 생깁니다. 패킷이 들어올 때마다 이 규칙을 위에서 아래로 순차 검색하는 구조라서, 클러스터가 커질수록 네트워크 성능이 선형적으로 저하됩니다. O(n) 룩업입니다.

홈랩이야 서비스가 수십 개 수준이니 체감이 덜하지만, 새로운 커넥션이 대량으로 발생하는 워크로드(예: 웹 크롤러, API 게이트웨이)에서는 차이가 체감됩니다. 새로 구축하는 클러스터라면 eBPF 기반 접근을 먼저 고려할 만합니다.

eBPF: 커널 레벨의 직행 열차

Cilium은 eBPF(extended Berkeley Packet Filter)를 사용해서 패킷을 커널 레벨에서 직접 처리합니다. iptables처럼 규칙을 순차 탐색하는 게 아니라 해시 테이블 기반 O(1) 룩업을 수행합니다. 서비스가 10개든 10,000개든 성능이 일정합니다.

추가로, DSR(Direct Server Return) 모드를 사용하면 응답 트래픽이 로드밸런서를 거치지 않고 클라이언트에게 직접 돌아갑니다. 워크로드 특성에 따라 다르지만, 로드밸런싱 홉이 줄어드는 만큼 지연을 상당히 줄일 수 있습니다.

빠르게 확산되는 채택 현황

Cilium은 최근 몇 년간 클라우드 네이티브 생태계에서 빠르게 채택되고 있습니다. Azure AKS는 Azure CNI powered by Cilium으로 eBPF 기반 데이터플레인을 기본 옵션으로 제공하기 시작했고, GKE도 Datapath V2를 통해 Cilium 기반 네트워킹을 지원합니다. EKS 환경에서도 eBPF 네트워킹을 원하는 팀들이 Cilium을 널리 사용하고 있습니다.

메이저 클라우드 3사가 모두 Cilium 기반 옵션을 갖추고 있다는 것은, 홈랩에서 Cilium을 익혀두면 실무 전환 시에도 그 경험이 그대로 통한다는 뜻이기도 합니다.


3. 사전 준비: Flannel과 kube-proxy 비활성화

Talos에서 Cilium을 쓰려면, 기본 CNI인 Flannel과 kube-proxy를 먼저 꺼야 합니다. 이 작업은 클러스터 생성 시점에 하는 것이 가장 깔끔합니다. 이미 Flannel로 동작 중인 클러스터를 전환하는 것도 가능하지만, 처음부터 Cilium으로 시작하는 것을 강력히 권장합니다.

패치 YAML 작성

# cilium-patch.yaml
cluster:
  network:
    cni:
      name: none        # Flannel 비활성화
  proxy:
    disabled: true      # kube-proxy 비활성화

이 두 줄이 핵심입니다. cni.name: none은 Talos에게 "CNI는 내가 알아서 설치할 테니 Flannel 깔지 마"라고 알려주는 것이고, proxy.disabled: true는 kube-proxy DaemonSet을 아예 배포하지 않겠다는 선언입니다.

클러스터 생성 시 패치 적용

# 시크릿 번들 생성
talosctl gen secrets -o secrets.yaml

# 패치를 포함해서 클러스터 설정 생성
talosctl gen config my-cluster https://[CONTROL_PLANE_VIP]:6443 \
  --with-secrets secrets.yaml \
  --config-patch @cilium-patch.yaml \
  --output-dir ./clusterconfig

기존 클러스터에 패치 적용 (이미 Flannel로 운영 중인 경우)

# 컨트롤 플레인 노드에 패치 적용
talosctl patch machineconfig \
  --nodes [CONTROL_PLANE_IP] \
  --patch @cilium-patch.yaml

# 워커 노드에도 동일하게 적용
talosctl patch machineconfig \
  --nodes [WORKER_1_IP] \
  --patch @cilium-patch.yaml

talosctl patch machineconfig \
  --nodes [WORKER_2_IP] \
  --patch @cilium-patch.yaml

⚠️ 중요: Flannel을 끄면 CNI가 없는 상태가 됩니다. 이 상태에서는 Pod 간 통신이 불가능하므로, Cilium을 최대한 빨리 설치해야 합니다. 부트스트랩 직후 바로 Cilium Helm 설치를 진행하세요.


4. Cilium 설치 — 3가지 방법 비교

Talos에서 Cilium을 설치하는 방법은 크게 3가지입니다. 각각의 장단점을 정리합니다.

방법 1: Helm (권장)

helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium \
  --namespace kube-system \
  --values cilium-values.yaml

장점: 값 커스터마이징이 자유롭고, 업그레이드가 helm upgrade 한 줄로 끝남. GitOps(Flux/ArgoCD)와 궁합이 좋음.

단점: Helm이 설치되어 있어야 함. 부트스트랩 시점에 CNI가 없어서 잠시 네트워킹이 끊긴 상태를 경험함.

방법 2: Inline Manifest (부트스트랩 자동화용)

Talos의 cluster.inlineManifests에 Cilium YAML을 직접 넣는 방법입니다. 부트스트랩 시 Talos가 자동으로 적용해서 CNI 없는 공백 시간을 없앨 수 있습니다.

# Helm으로 YAML 렌더링
helm template cilium cilium/cilium \
  --namespace kube-system \
  --values cilium-values.yaml > cilium-manifest.yaml

그런 다음 이 YAML을 machine config의 cluster.inlineManifests에 삽입합니다.

장점: 부트스트랩 시 CNI가 즉시 적용됨. 네트워킹 공백 없음.

단점: manifest가 길어서 machine config가 비대해짐. 업데이트 시 config 수정 후 talosctl upgrade-k8s 필요.

⚠️ Inline Manifest의 핵심 주의사항: Talos는 인라인 매니페스트를 생성(create)만 합니다. 한번 적용된 리소스의 삭제나 업데이트는 Talos가 자동으로 해주지 않습니다. Cilium 버전을 올리거나 설정을 변경할 때는 모든 컨트롤 플레인 machine config를 먼저 수정한 뒤 talosctl upgrade-k8s를 실행해야 합니다. 이 동작 방식을 모르면 "설정을 바꿨는데 반영이 안 된다"는 상황에 빠질 수 있습니다.

⚠️ Helm 템플릿으로 네임스페이스를 변경해도 네임스페이스 생성 YAML은 자동 포함되지 않습니다. 인라인 매니페스트 최상단에 네임스페이스 YAML을 수동으로 넣어주세요.

방법 3: Cilium CLI

cilium install --helm-values cilium-values.yaml

장점: 가장 간단함. 설치 후 자동 검증까지 해줌.

단점: cilium CLI가 별도로 설치되어 있어야 함. 내부적으로 Helm을 사용하긴 하지만 세밀한 제어가 어려움.

결론

홈랩에서는 Helm 방식을 권장합니다. GitOps로 관리하기 편하고, 업그레이드가 깔끔합니다. 완전 자동화된 부트스트랩이 필요하다면 Inline Manifest를 고려하세요.


5. Helm으로 Cilium 설치하기 — values.yaml 완전 해설

전체 values.yaml

# cilium-values.yaml
# Talos Linux 전용 Cilium Helm Values

# === Talos 필수 설정 ===
# Talos는 cgroup을 직접 마운트하므로 Cilium의 자동 마운트를 끈다
cgroup:
  autoMount:
    enabled: false
  hostRoot: /sys/fs/cgroup

# IPAM: Kubernetes 기본 모드 사용
ipam:
  mode: kubernetes

# Talos KubePrism 엔드포인트
# Talos는 localhost:7445로 K8s API를 노출함
k8sServiceHost: localhost
k8sServicePort: 7445

# === kube-proxy 대체 ===
# true로 설정하면 Cilium이 kube-proxy 역할을 완전히 대신함
kubeProxyReplacement: true

# === 보안 컨텍스트 ===
# Talos의 보안 모델에 맞는 최소 권한 설정
securityContext:
  capabilities:
    ciliumAgent:
      - CHOWN
      - DAC_OVERRIDE
      - FOWNER
      - IPC_LOCK
      - KILL
      - NET_ADMIN
      - NET_RAW
      - SETGID
      - SETUID
      - SYS_ADMIN
      - SYS_RESOURCE
    cleanCiliumState:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE

# === Hubble (관측성) ===
hubble:
  enabled: true
  relay:
    enabled: true
  ui:
    enabled: true
  metrics:
    enabled:
      - dns
      - drop
      - tcp
      - flow
      - icmp
      - http

# === 추가 권장 설정 ===
# L2 Announcement (MetalLB 대체, 홈랩에서 유용)
l2announcements:
  enabled: true

# 엔드포인트 상태를 CRD에 저장 (대규모 클러스터 성능 향상)
endpointStatus:
  enabled: true
  status: "policy"

주요 설정값 상세 해설

cgroup 관련: Talos는 불변 OS이므로 cgroup 마운트를 OS가 직접 관리합니다. Cilium이 자동으로 마운트를 시도하면 충돌이 나기 때문에 autoMount.enabled: false로 끄고, 호스트의 cgroup 경로(/sys/fs/cgroup)를 직접 지정합니다.

k8sServiceHost/Port: 일반 K8s에서는 보통 kubernetes.default.svc를 쓰지만, Talos는 KubePrism이라는 자체 API 프록시가 모든 노드에서 localhost:7445를 통해 API 서버에 접근하게 해줍니다. Cilium이 이 로컬 엔드포인트를 사용하도록 설정하면 외부 로드밸런서 없이도 안정적으로 동작합니다.

kubeProxyReplacement: true: 이 한 줄이 kube-proxy를 완전히 대체합니다. iptables 규칙 대신 eBPF 해시 맵으로 서비스 라우팅을 처리하게 됩니다.

securityContext: Talos는 커널 모듈 로딩을 허용하지 않으므로 SYS_MODULE은 빠져 있습니다. 위에 나열된 capability들은 Cilium이 eBPF 프로그램을 로드하고 네트워크를 관리하기 위한 최소 권한입니다.

설치 실행

# Helm 레포 추가
helm repo add cilium https://helm.cilium.io/
helm repo update

# Cilium 설치
helm install cilium cilium/cilium \
  --namespace kube-system \
  --values cilium-values.yaml

# 설치 진행 상황 확인
kubectl -n kube-system get pods -l app.kubernetes.io/part-of=cilium -w

설치 후 1~2분 내에 cilium-agent, cilium-operator, hubble-relay, hubble-ui Pod가 Running 상태로 올라옵니다.

NAME                               READY   STATUS    RESTARTS   AGE
cilium-xxxxx                       1/1     Running   0          90s
cilium-yyyyy                       1/1     Running   0          90s
cilium-zzzzz                       1/1     Running   0          90s
cilium-operator-xxxxx-yyyyy        1/1     Running   0          90s
hubble-relay-xxxxx-yyyyy           1/1     Running   0          90s
hubble-ui-xxxxx-yyyyy              2/2     Running   0          90s


6. Hubble UI로 네트워크 관측성 확보하기

지난 글에서 "SSH 없이 어떻게 운영하느냐"라는 질문에 "관측성 도구가 답"이라고 했습니다. Hubble은 그 답의 핵심 조각입니다.

Hubble이란?

Hubble은 Cilium에 내장된 네트워크 관측성 도구입니다. eBPF가 커널 레벨에서 수집하는 모든 네트워크 플로우를 시각화해 줍니다. 어떤 Pod가 어떤 서비스에, 어떤 프로토콜로, 얼마나 자주 통신하는지를 실시간으로 볼 수 있습니다.

Hubble UI 접속

위 values.yaml에서 이미 Hubble을 활성화했으므로, 포트 포워딩만 하면 바로 접속할 수 있습니다.

# Hubble UI 포트 포워딩
kubectl port-forward -n kube-system svc/hubble-ui 12000:80

# 브라우저에서 접속
# http://localhost:12000

Hubble CLI로 실시간 플로우 확인

# Hubble CLI 설치
export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all \
  https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-amd64.tar.gz
tar xzvf hubble-linux-amd64.tar.gz
sudo mv hubble /usr/local/bin/

# 포트 포워딩 (별도 터미널)
kubectl port-forward -n kube-system svc/hubble-relay 4245:80

# 실시간 플로우 관찰
hubble observe --follow

# 특정 네임스페이스의 드롭된 패킷만 필터링
hubble observe --namespace default --verdict DROPPED

# DNS 쿼리 추적
hubble observe --protocol DNS

SSH가 없는 Talos에서 "지금 네트워크에 무슨 일이 일어나고 있는지"를 파악하는 가장 강력한 방법입니다. 장애 발생 시 hubble observe --verdict DROPPED로 드롭된 패킷을 추적하면 원인 파악이 빠릅니다.

Hubble 메트릭과 Grafana 연동

values.yaml에서 hubble.metrics.enabled로 수집한 메트릭은 Prometheus가 스크래핑할 수 있습니다. Grafana에 Cilium 공식 대시보드(ID: 16611)를 임포트하면 네트워크 레이턴시, 처리량, DNS 쿼리 통계를 한눈에 볼 수 있습니다.


7. 트러블슈팅

13년 동안 인프라를 만지면서 배운 교훈 하나: 설치 가이드는 무조건 트러블슈팅이 있어야 진짜입니다. 제가 직접 겪었거나 커뮤니티에서 빈번하게 보고되는 이슈들을 정리합니다.

이슈 1: CoreDNS가 동작하지 않는다

증상: Pod에서 서비스 이름 해석이 안 됨. nslookup kubernetes.default.svc.cluster.local 실패.

원인: Talos의 forwardKubeDNSToHost 옵션이 true(기본값)인 상태에서 Cilium의 eBPF Host-Routing과 충돌하는 경우가 있습니다. 환경에 따라 다를 수 있지만, 이 조합이 문제를 일으키는 사례가 커뮤니티에서 자주 보고됩니다.

해결: 둘 중 하나를 조정하면 됩니다. 환경에 맞는 쪽을 선택하세요.

방법 A — Talos 쪽 설정 변경:

# Talos machine config에 추가
machine:
  features:
    kubernetesTalosAPIAccess:
      enabled: true
    hostDNS:
      enabled: true
      forwardKubeDNSToHost: false  # Host DNS 포워딩 비활성화

방법 B — Cilium 쪽에서 host legacy routing 활성화:

# cilium-values.yaml에 추가
bpf:
  hostLegacyRouting: true

💡 방법 A가 더 깔끔한 접근이지만, 호스트 DNS에 의존하는 다른 서비스가 있다면 방법 B를 선택하는 것이 안전합니다.

이슈 2: Cilium connectivity test에서 pod-to-service 실패

증상: cilium connectivity test 실행 시 대부분 통과하지만 pod-to-service 관련 테스트만 실패.

원인: Pod Security Admission에서 non-default capability를 차단하는 경우가 많습니다.

해결:

# connectivity test 네임스페이스에 privileged 레이블 추가
kubectl create namespace cilium-test
kubectl label namespace cilium-test \
  pod-security.kubernetes.io/enforce=privileged

이슈 3: 노드 간 Pod 통신 불가

증상: 같은 노드의 Pod끼리는 통신되지만, 다른 노드의 Pod와는 통신 안 됨.

원인: Cilium이 사용하는 VXLAN 포트(기본 8472/UDP)가 Proxmox 방화벽이나 호스트 네트워크 설정에서 차단된 경우가 많습니다. 특히 Proxmox 방화벽을 활성화한 환경에서 빈번합니다.

해결: Proxmox 호스트 및 VM 네트워크 설정을 함께 확인해야 합니다.

# Proxmox 호스트에서 VXLAN 포트 확인
iptables -L -n | grep 8472

# Talos 노드에서 VXLAN 인터페이스 확인
talosctl -n [NODE_IP] get links | grep cilium

Proxmox 방화벽을 사용 중이라면 UDP 8472 포트를 명시적으로 허용합니다. 또한 Proxmox의 VM 네트워크 브릿지 설정, 호스트 레벨의 방화벽 규칙도 함께 점검하세요. 환경에 따라 차단 지점이 다를 수 있습니다.

이슈 4: Cilium Agent CrashLoopBackOff

증상: cilium-agent Pod가 반복적으로 재시작됨.

원인: 대부분 cgroup 설정 누락이 원인입니다. Talos에서 cgroup.autoMount.enabledfalse로 설정하지 않으면 Cilium이 cgroup을 마운트하려다 실패합니다.

해결: 위 values.yaml의 cgroup 섹션이 정확히 적용되었는지 확인합니다.

# Cilium Agent 로그 확인
kubectl -n kube-system logs -l k8s-app=cilium --tail=50

# 설정값 확인
helm get values cilium -n kube-system

8. 설치 검증 및 결과 확인

cilium status 확인

cilium status

정상이라면 아래와 비슷한 출력이 나옵니다:

    /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    disabled (using embedded mode)
 \__/¯¯\__/    Hubble Relay:       OK
    \__/       ClusterMesh:        disabled

Deployment             cilium-operator    Desired: 1, Ready: 1/1
DaemonSet              cilium             Desired: 3, Ready: 3/3
Deployment             hubble-relay       Desired: 1, Ready: 1/1
Deployment             hubble-ui          Desired: 1, Ready: 1/1
Containers:            cilium             Running: 3
                       cilium-operator    Running: 1
                       hubble-relay       Running: 1
                       hubble-ui          Running: 1

kube-proxy가 진짜 없는지 확인

# kube-proxy DaemonSet이 존재하지 않아야 함
kubectl -n kube-system get ds kube-proxy
# Error from server (NotFound): daemonsets.apps "kube-proxy" not found

# iptables 규칙이 최소인지 확인 (Talos 노드에서)
talosctl -n [NODE_IP] read /proc/net/ip_tables_names

Cilium connectivity test

# 전체 연결성 테스트 (5~10분 소요)
cilium connectivity test

# 빠른 검증만 하려면
cilium connectivity test --test pod-to-pod
cilium connectivity test --test pod-to-service

모든 테스트가 통과하면 Cilium이 정상적으로 kube-proxy를 대체하고 있다는 뜻입니다.

실제 워크로드로 검증

# 테스트 디플로이먼트 생성
kubectl create deployment nginx --image=nginx --replicas=3
kubectl expose deployment nginx --port=80

# Pod IP로 직접 통신 확인
kubectl run tmp --image=busybox --rm -it --restart=Never \
  -- wget -qO- http://nginx.default.svc.cluster.local

# 결과: nginx 기본 페이지 HTML이 출력되면 성공


9. 마무리: iptables 안녕, eBPF 어서와

정리하면, 오늘 한 작업은 이렇습니다:

  1. Talos machine config에서 Flannel과 kube-proxy를 비활성화했습니다
  2. Cilium을 Helm으로 설치하고 eBPF 기반 kube-proxy 대체를 활성화했습니다
  3. Hubble로 네트워크 관측성을 확보했습니다
  4. connectivity test로 전체 네트워킹이 정상 동작하는 것을 검증했습니다

SSH도 없고, iptables도 없고, kube-proxy도 없는 클러스터가 완성됐습니다. 순수하게 eBPF와 gRPC API로만 돌아가는 쿠버네티스. Talos의 불변 철학과 Cilium의 eBPF 네트워킹이 만나면 이런 극단적으로 깔끔한 구조가 나옵니다.

금요일 밤, Hubble UI에서 Pod 간 트래픽이 초록색 선으로 흐르는 것을 보면서 라면 국물을 마시는 순간... 이 취미를 왜 하는지 다시 한번 확인합니다. 😄

다음 편 예고

다음 글에서는 ingress-nginx의 공식 은퇴(2026년 3월 아카이브)에 맞춰 Cilium Gateway API로 마이그레이션하는 이야기를 다룹니다. 2026년 3월 24일, ingress-nginx 리포지토리가 아카이브되었고 3월 31일부로 EOL이 선언됐습니다. 오늘 설치한 Cilium 위에 Gateway API를 올려서 Ingress를 완전히 대체하는 과정을 실전으로 다뤄보겠습니다.


참고 링크