90DaysOfDevOps (Day 36)

고태규·2025년 11월 1일
0

DevOps

목록 보기
35/50
post-thumbnail

해당 스터디는 90DaysOfDevOps
https://github.com/MichaelCade/90DaysOfDevOps
를 기반으로 진행한 내용입니다.

Day 36 - Policy-as-Code Super-Powers! Rethinking Modern IaC With Service Mesh And CNI


1. IaC와 K8S, Cilium


해당 프레젠테이션 데모는 IaC 리포지토리를 기반으로 진행된다.

IaC는 인프라를 선언적 (declaratively)이고 구체적으로 작성하여, 신뢰할 수 있고 반복 가능한 배포를 보장하는 방법론이다.

데모에 사용되는 핵심 도구는 다음과 같다.

  1. Pulumi: IaC를 코딩하고 배포하는 데 사용 (해당 데모는 TypeScript 사용)

  2. Kubernetes (kind, Kubernetes in Docker): 인프라가 배포될 플랫폼으로, Docker 컨테이너 기반으로 로컬 Kubernetes 클러스터를 실행하는 도구

  3. Cilium: 쿠버네티스의 CNI (Container Network Interface) 역할을 하며, 강력한 네트워크 보안 정책을 배포하는 데 사용된다.

추가적으로, CNI는 단순한 컨테이너 간 통신을 넘어, OSI 스택을 거슬러 올라가 서비스 메시 (Service Mesh) 레이어까지 다루는 수준으로 진화하였음을 강조하였다.


2. 왜 Flannel이 아닌 Cilium인가?


기본 CNI인 flannel이 표준 쿠버네티스 네트워크 정책 (Network Policy)을 지원하지 않는다는 점을 한계로 지적된다.

반면 Cilium은 eBPF 기술을 활용하여 막강한 기능을 제공한다.

  • 커널 스페이스 정책 적용: eBPF를 통해 네트워크 정책이 커널 스페이스에서 직접 적용되고 강제됨. ('소켓 열림'과 같은 이벤트를 커널 레벨에서 즉시 감지하고 실시간으로 정책을 적용할 수 있음을 의미)

  • 확장된 정책 기능: 표준 쿠버네티스 네트워크 정책을 넘어, 클러스터 전반 (Cluster-wide), 노드 전반 (Node-wide), 심지어 레이어 7 (L7) 정책까지 매우 세분화된 제어가 가능

  • 네트워킹 및 메시: 보안 외에도, 여러 클러스터를 논리적으로 하나로 묶는 브리징 (Cluster Mesh) 기능이나 서비스 메시 기능까지 제공한다.

추가적으로, Cilium이 채택한 eBPF (extended Berkeley Packet Filter) 기술이란, 리눅스 커널에서 일어나는 다양한 이벤트들에 대해 사용자 정의된 함수들이 커널 내 샌드박스 환경에서 동작하게끔 하는 기술이다.

따라서, 커널을 재컴파일하거나 위험한 모듈을 로드할 필요 없이, 실행 중인 리눅스 커널의 동작을 안전하고, 동적으로, 그리고 거의 네이티브에 가까운 속도로 프로그래밍할 수 있게 되었다.

Cilium은 이 eBPF 기술을 활용하여 쿠버네티스의 네트워킹, 보안, Observability를 구현하는 CNI이다.

2-1. 네트워크 정책 (보안) 측면

  • Flannel:

    • CNI 규격의 핵심인 파드 IP 할당 및 연결에만 집중

    • 쿠버네티스 NetworkPolicy 리소스를 해석하거나 적용하는 기능 자체가 없음.

    • 따라서 Flannel 환경에서는 모든 파드가 서로 통신할 수 있는 'allow-all' 상태가 기본값

  • Cilium:

    • eBPF를 사용하여 커널 수준에서 직접 패킷을 필터링하므로, NetworkPolicy가 핵심 기능

      • L3/L4 정책: IP, 포트, 레이블을 기반으로 한 표준 NetworkPolicy를 eBPF 프로그램을 통해 구현

      • L7 정책 (향상점): eBPF는 커널에서 패킷 payload를 파싱할 수 있음. Cilium은 이 기능을 활용해 HTTP, gRPC, Kafka 등 애플리케이션 프로토콜을 인식함. 이를 통해 HTTP GET /api/read는 허용하고 POST /api/admin은 거부하는 등 L7 수준의 정교한 보안 정책을 적용할 수 있음.


2-2. 서비스 라우팅 (성능 및 확장성) 측면

  • Flannel:

    • Flannel은 '서비스(Service)' 오브젝트의 라우팅을 처리하지 않으며, 해당 역할은 kube-proxy에 의존

    • kube-proxy는 iptables를 사용하며, 이는 서비스와 파드 수가 증가할수록, iptables 규칙 체인이 선형적(O(n))으로 길어져 성능 저하를 유발하는 확장성 문제를 가짐.

  • Cilium:

    • eBPF를 사용하여 kube-proxy의 모든 기능을 대체할 수 있음.

    • 서비스 IP -> 파드 IP 목록의 매핑을 iptables의 순차 리스트가 아닌 커널 내 eBPF 해시맵에 저장

    • 해시맵은 서비스 수와 관계없이 상수 시간 O(1)의 조회 속도를 보장하므로, 클러스터 규모가 수천 개의 서비스로 커져도 서비스 라우팅 성능이 저하되지 않음.


2-3. 관찰 가능성 (Observability) 측면

  • Flannel:

    • 기본적인 패킷 카운터 외에는 가시성을 제공하지 않음.

    • VXLAN 캡슐화로 인해 호스트에서 tcpdump를 사용한 트러블슈팅이 복잡함.

  • Cilium:

    • eBPF가 커널의 모든 네트워크 이벤트 훅에 위치하므로, 모든 네트워크 흐름을 실시간으로 감시할 수 있음.

      • Hubble: 해당 데이터를 기반으로 Hubble이라는 강력한 관찰 가능성 도구를 기본 제공

      • L7 가시성: Hubble은 L3/L4 흐름뿐만 아니라, HTTP 503 응답, gRPC 호출 지연 시간, DNS 쿼리 실패 등 L7 수준의 트래픽을 시각화하고 모니터링할 수 있음.

      • 정책 문제 해결: "패킷이 어떤 정책에 의해 차단되었는지"를 명확하게 보여주어 트러블슈팅이 매우 용이


2-4. 데이터 경로 (네트워크 오버헤드) 측면

  • Flannel:

    • 주로 VXLAN 오버레이를 사용

    • 모든 노드 간 파드 트래픽에 캡슐화/역캡슐화 오버헤드 (추가적인 CPU 사이클 및 패킷 헤더 크기 증가)를 발생시킴.

  • Cilium:

    • eBPF를 통해 더 효율적인 데이터 경로를 제공

      • Direct Routing (직접 라우팅) : L2/L3 네트워크 환경이 지원되면 캡슐화 없이(no-overlay) 파드 IP로 직접 라우팅하여 bare-metal에 가까운 네트워크 성능을 제공

      • VXLAN, Geneve 등 오버레이 모드도 지원


이러한 기존 CNI인 Flannel보다 다양한 측면에서의 장점이 존재하여 Cilium을 채택하였다.


3. Pulumi로 Cilium 배포하기


해당 프레젠테이션에서는 VS Code에서 직접 GitHub Codespaces 환경을 실행하며 데모를 시작한다.

해당 환경에는 모든 의존성이 미리 설치된 개발 컨테이너가 포함되어 있다.

3-1단계: 쿠버네티스 클러스터 생성

먼저, Makefile에 정의된 명령어를 사용하여 kind 쿠버네티스 클러스터를 생성한다.

make kind

해당 클러스터는 의도적으로 CNI와 Kube-proxy 없이 설치된다.

3-2단계. 리포지토리 및 코드 분석

index.ts : Pulumi로 작성된 메인 IaC 프로그램 (Pulumi는 Python, Go, YAML 등도 지원)

Pulumi.yaml : 프로젝트 이름 등 기본 설정을 정의

Makefile : kind 클러스터 생성과 같은 편의 스크립트를 포함

index.ts 파일의 핵심은 Pulumi의 쿠버네티스 패키지(@pulumi/kubernetes)를 사용하여 Cilium 헬름 차트를 배포하는 부분이다.

// index.ts (예시)
import * as k8s from "@pulumi/kubernetes";

// ...

const ciliumChart = new k8s.helm.v3.Release("cilium", {
    chart: "cilium",
    version: "1.12.0", // 예시 버전
    namespace: "kube-system",
    repositoryOpts: {
        repo: "https://helm.cilium.io/",
    },
    values: {
        // CNI와 Kube-proxy를 대체하도록 설정
        kubeProxyReplacement: "strict",
        routingMode: "native",
        // ...
    },
});

여기서 kubeProxyReplacement: "strict" 설정은 Cilium이 Kube-proxy의 모든 기능을 대체하도록 지시하는 중요한 값이다.

3-3단계. 클러스터 상태 확인 (CNI 배포 전)

kind 클러스터 생성이 완료된 후, kube-system 네임스페이스의 파드를 확인한다. CoreDNS 파드들이 'Pending' 상태로 멈춰 있다.

CoreDNS가 작동하기 위해서는 IP 연결이 필요하고, 이 연결은 CNI가 제공해야 하므로 CNI가 없는 현재 상태에서는 당연한 결과이다.

3-4단계. Pulumi를 통한 IaC 배포

이제 Pulumi를 사용하여 정의된 인프라를 배포한다.

  • pulumi login : (Codespaces 환경 변수에 저장된 토큰을 사용하여) Pulumi 서비스에 로그인

  • pulumi stack init dev : 'dev'라는 이름의 새 스택(배포 환경)을 생성

  • pulumi up --skip-preview : 미리 보기 없이 즉시 배포를 시작

해당 명령어를 순차적으로 실행하게 되면, Pulumi가 index.ts에 정의된 대로 Cilium 헬름 차트를 포함한 모든 리소스를 쿠버네티스 클러스터에 생성하기 시작한다.

3-5단계. 배포 리소스 확인 (스타워즈 데모)

해당 데모는 '스타워즈' 테마를 사용하며, 3개의 파드 (Death Star, TIE Fighter, X-Wing)와 1개의 CiliumNetworkPolicy (Cilium CustomResource)를 배포한다.

  • 정책 이름 : Rule one

  • 정책 로직 : TIE Fighter만 Death Star에 착륙 (연결)할 수 있다. X-Wing은 접근이 금지된다.

  • 구현 : 해당 정책은 org=Empire (제국군) 레이블을 가진 파드에서 오는 트래픽만 허용한다. TIE Fighter와 Death Star는 org=Empire 레이블을 가지며, X-Wing은 org=Alliance (연합군) 레이블을 가진다.

3-6단계. 데모 검증

pulumi up이 성공적으로 완료되면, 정책이 올바르게 작동하는지 검증한다.

  1. 검증 1 (TIE Fighter \rightarrow Death Star):

    • TIE Fighter 파드에 kubectl exec로 접속한다.

    • curl을 사용해 Death Star 서비스(default 네임스페이스)로 요청을 보낸다.

    • 결과: 성공.

  2. 검증 2 (X-Wing \rightarrow Death Star):

    • X-Wing 파드에 kubectl exec로 접속한다.

    • curl -v를 사용해 Death Star 서비스로 요청을 보낸다.

    • 결과: 실패. curl 명령어가 연결을 시도하지만 응답 없이 타임아웃된다.

  3. 검증 3 (정책 확인):

    • k9s (터미널 UI)를 사용하여 default 네임스페이스의 CiliumNetworkPolicy 리소스를 확인한다.

    • YAML 정의에서 org=Empire 레이블을 가진 endpoint에서 오는(ingress) 트래픽만 허용하도록 명시된 것을 확인한다.

    • Cilium 네트워크 정책 리소스에 기본적으로 '암묵적인 모두 거부(implicit deny all)' 규칙이 있어, 허용된 트래픽 외에는 모두 차단된다.


이로써 해당 프레젠테이션은 Pulumi (IaC)를 통해 쿠버네티스 클러스터에 Cilium (CNI)과 eBPF 기반의 L3/L4 네트워크 보안 정책을 성공적으로 배포하고 검증하였다.


4. Ops의 진화: '명령형'에서 '선언형'으로


운영(Ops) 방식은 시간이 지남에 따라 크게 진화했다.

1. 명령형(Imperative) 운영: 절차적 방식의 한계

초기 운영 방식은 명령형(Imperative) (어떻게(How)에 초점을 맞추는 절차적 방식)

  • 새 서비스 배포를 위해 서버 하드웨어를 요청하고, OS를 프로비저닝하며, CLI 명령어를 입력하거나 배시 스크립트를 작성하는 등 모든 단계를 순서대로 직접 수행해야 했음.

  • 해당 방식은 '클릭 옵스(ClickOps)'라고도 불리며, 과정이 복잡하고 사람의 실수(Human Error)에 매우 취약함.

2. 자동화(Automation)의 함정

이러한 문제를 해결하기 위해 '자동화'가 도입되었지만, 초기 자동화 역시 명령형 방식의 연장선에 불과한 경우였음.

  • 자동화 스크립트가 여전히 "어떻게"를 정의하고 있다면, 변수가 빠지거나 잘못된 값이 입력되었을 때 자동화는 잘못된 명령을 그대로 수행하는 문제점 존재

  • 단순히 배포에 실패하는 것을 넘어, 기존 인프라에 적극적인 손상(damage)을 일으킬 수 있는 위험을 내포

3. 선언형(Declarative) 운영: "무엇을(What)"에 집중하다

업계가 쿠버네티스 (Kubernetes)와 같은 API 기반 시스템으로 성숙하면서 선언형(Declarative) 운영 방식이 주류가 되었음.

  • 선언형 방식은 "어떻게(How)"가 아닌 "원하는 상태(Desired State)"가 무엇인지(What)를 선언하는 데 집중

  • 사용자가 "A라는 상태를 원한다"고 시스템에 선언하면, 시스템 (ex. Kubernetes Controller)이 현재 상태를 파악하고 원하는 상태에 도달하기 위한 작업을 스스로 수행

  • 오늘날 인프라스트럭처 코드(Infrastructure as Code, IaC)의 핵심 철학으로, IaC를 사용하면 성공적인 배포 상태를 코드로 정의하고, 이 코드를 통해 언제든지 동일한 성공을 재현하거나 반복/구축 가능

  • 운영의 정신적 부담과 지루함을 획기적으로 감소


5. 네트워크 자동화


이러한 '선언형' 패러다임은 네트워킹 영역에서도 동일하게 적용된다.

1. 역할의 융합과 코드 기반 구성

과거 분리되었던 플랫폼 엔지니어와 네트워크 엔지니어의 역할이 클라우드 네이티브 환경에서는 매우 밀접하게 융합되고 있다.

  • Istio, Cilium, Envoy, Kubernetes, OTEL (OpenTelemetry) 등 오늘날의 핵심 인프라 구성요소는 모두 코드로 구성(Configuration as Code)이 가능

  • 네트워크 엔지니어 역시 Python이나 TypeScript와 같은 범용 프로그래밍 언어를 학습하여, Pulumi 같은 도구로 네트워크 인프라 프로비저닝을 자동화할 수 있음.

2. 컨테이너 네트워킹의 복잡성과 CNI의 역할

OSI 모델 자체는 변하지 않았지만, 컨테이너 환경의 네트워킹은 내부적으로 매우 복잡하다.

  • 컨테이너 하나를 네트워크에 연결하기 위해, 리눅스 내부에서는 네트워크 네임스페이스 생성, 가상 와이어(veth) 연결, 가상 스위치(bridge) 설정, 호스트 출구 인터페이스 연결 등 수많은 명령형 작업이 필요

  • CNI(Container Network Interface)는 바로 이 복잡한 온보딩 (네트워크 부여) 및 오프보딩 (네트워크 제거) 프로세스를 자동화하기 위해 존재

3. IPAM과 '원하는 상태'로서의 네트워크

  • CNI는 컨테이너 네트워킹의 추가적인 역할인 IPAM(IP 주소 관리)을 완벽하게 추상화함.

  • 과거 VM 시절 수동으로 IP를 할당, 추적하던 방식과 달리, CNI가 이 모든 과정을 자동으로 처리

  • 이러한 자동화를 통해, 네트워크 관리 방식이 근본적으로 바뀜.

  • ex : BGP 설정을 위해 라우터 CLI에 직접 로그인하여 명령어를 입력하는 대신, Pulumi와 TypeScript로 '원하는 BGP 상태'를 코드로 작성할 수 있음.

  • 해당 코드를 pulumi up으로 실행하면, Pulumi가 알아서 컨트롤 플레인과 상호작용하여 해당 상태를 구현함.


6. IaC의 본질


앞서 설명한 모든 것을 가능하게 하는 기반이 바로 IaC(Infrastructure as Code)이다.

IaC가 중요한 이유는 단순히 코드로 인프라를 제어하여 인간의 실수를 줄이고 반복성을 높이는 데만 있지 않다.

IaC의 진정한 힘은, 수백 가지 기술에 걸쳐 존재하는 수천 개의 리소스 객체를 제어하여 최종적으로 애플리케이션을 고객에게 전달하는, 이 거대한 복잡성을 관리할 수 있게 해주는 핵심 조각이라는 점에서 발휘된다.

IaC는 "어떻게" 수행해야 하는지 절차를 나열하는 대신, "무엇을" 원하는지 최종 결과 (outcome)를 코드 (code)로 작성할 수 있게 해주는 핵심 방법론이다.


0개의 댓글