반응형

 

Cilium 은 네트워크의 다양한 계층에서 보안 기능을 제공합니다. Layer 3에서는 Identity-Based 보안을 제공하고, Layer 4에서는 Port Level 보안을, Layer 7에서는 Application protocol Level 보안을 지원합니다.

 

Layer 3 Identity-Based

Security-ID

저번 시간에 공유드렸듯이, 쿠버네티스 환경에서 파드들의 IP 가 수시로 변하기 때문에 ip 기반 접근 통제 보다는 id 기반 접근 정책을 통해 파드들의 통신을 제어합니다. Cilium 에서는 모든 엔드포인트에 대해 고유한 ID 가 할당됩니다. 이 ID 는 클러스터 내에서 유일한 ID 로 구성되며, 동일한 Security 정책을 사용하는 엔드포인트들은 동일한 ID 를 공유하게 됩니다.

 

 

위와 같이 ciliumendpoint 들을 조회했을 때, 동일한 보안 정책을 부여받는 엔드포인트끼리 동일한 security ID 값을 부여받는 것을 알 수 있습니다.

 

파드 또는 컨테이너가 시작되면 Cilium 은 컨테이너 런타임에서 수신한 이벤트를 기반으로 엔드포인트를 생성합니다. 다음 단계로 Cilium 은 생성된 엔드포인트의 ID 를 확인하며, 포드나 컨테이너의 Labels가 변경될 때마다 ID 가 재확인되고 필요에 따라 자동으로 수정됩니다.
Labels 변경 시 endpoint가 waiting-for-identity 상태로 전환되어 새로운 identity 를 할당받게 됩니다. 이로 인해 security labels 와 관련된 네트워크 정책도 자동으로 재적용됩니다. 예를 들어 간단한 pod을 생성하면 초기 security identity 가 할당되고, kubectl label 명령으로 labels 를 변경하면 새로운 identity 값으로 업데이트되는 것을 확인할 수 있습니다.

 

 

참고로 security label 중 reserved 문자열 접두사가 붙은 라벨들은 Cilium 에서 관리하지 않는 네트워크 엔드포인트와의 통신을 허용하기 위해 예약된 라벨들입니다.

 

엔드포인트 기반 정책

두 엔드포인트가 Cilium에 의해 관리되고 레이블이 할당되어 있는 경우 유용합니다. 이 방법의 장점은 IP 주소가 정책에 하드코딩되지 않고 유연하게 대처가 가능하다는 점입니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l3-rule"
spec:
  endpointSelector:
    matchLabels:
      role: backend
  ingress:
  - fromEndpoints:
    - matchLabels:
        role: frontend

 

서비스 기반 정책

kubernetes 의 서비스 엔드포인트로 정책을 구성합니다. 이 엔드포인트는 서비스의 모든 백엔드 IP 주소를 포함하도록 자동으로 유지됩니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "service-rule"
spec:
  endpointSelector:
    matchLabels:
      id: app2
  egress:
  - toServices:
    # Services may be referenced by namespace + name
    - k8sService:
        serviceName: myservice
        namespace: default
    # Services may be referenced by namespace + label selector
    - k8sServiceSelector:
        selector:
          matchLabels:
            env: staging
        namespace: another-namespace

 

엔티티 기반 정책

혹은 앞서 살펴본 reserved 된 cilium identity 로도 네트워크 정책을 구성할 수 있습니다. IP 주소로 설명이 되지 않지만 원격 피어를 구성 정책에 활용할 수 있습니다. 로컬 호스트에 대한 연결이나 클러스터 외부에 대한 모든 연결이 포함됩니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "dev-to-kube-apiserver"
spec:
  endpointSelector:
    matchLabels:
      env: dev
  egress:
    - toEntities:
      - kube-apiserver

 

노드 기반 정책

특정 노드를 기반으로도 네트워크 정책을 구성할 수 있습니다. 특정 노드의 접근만 허용하거나 차단할 수 있습니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "to-prod-from-control-plane-nodes"
spec:
  endpointSelector:
    matchLabels:
      env: prod
  ingress:
    - fromNodes:
        - matchLabels:
            node-role.kubernetes.io/control-plane: ""

 

IP/CIDR 기반 정책

원격 피어가 엔드포인트가 아닌 경우 외부 서비스와 네트워크 정책을 구성할 때 유용합니다. IP 주소 또는 서브넷을 정책에 직접 지정해야 합니다. 이 구성은 안정적인 IP 또는 서브넷 할당이 필요하므로 최후의 수단으로 사용해야 합니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "cidr-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  egress:
  - toCIDR:
    - 20.1.1.1/32
  - toCIDRSet:
    - cidr: 10.0.0.0/8
      except:
      - 10.96.0.0/12

 

DNS 기반 정책

DNS 조회를 통해 IP 로 변환된 DNS 이름을 사용하여 원격의 비클러스터 피어를 선택하여 정책을 구성할 수도 있습니다.

보통 DNS 는 L7 기반 정책이므로 Envoy Proxy 가 대신 수행할 것으로 보이지만, L3 에서도 경량의 DNS Proxy 가 트래픽을 처리하여 가능하다고 하네요. DNS TTL 은 준수됩니다.

 

DNS 요청을 허용하는 L7 정책이 구성되어 있어야 하기 때문에 --enable-l7-proxy=true 옵션이 활성화 되어 있어야 합니다. 두 가지 패턴을 사용합니다.

  • toFQDNs.matchName: 정확히 일치하는 도메인 입력
  • toFQDNs.matchPattern: 와일드카드 패턴 입력
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "to-fqdn"
spec:
  endpointSelector:
    matchLabels:
      app: test-app
  egress:
    - toEndpoints:
      - matchLabels:
          "k8s:io.kubernetes.pod.namespace": kube-system
          "k8s:k8s-app": kube-dns
      toPorts:
        - ports:
           - port: "53"
             protocol: ANY
          rules:
            dns:
              - matchPattern: "*"
    - toFQDNs:
        - matchName: "my-remote-service.com"

 

Layer 4 Port Level

cilium 에서 프로토콜 기반 제어를 제공합니다. 해당 계층 정책은 3계층 정책과 함께 또는 독립적으로 사용할 수 있습니다.

포트를 지정해서 사용할 수도 있고,

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l4-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  egress:
    - toPorts:
      - ports:
        - port: "80"
          protocol: TCP

 

포트 범위를 지정할 수도 있고,

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l4-port-range-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  egress:
    - toPorts:
      - ports:
        - port: "80"
          endPort: 444
          protocol: TCP

 

CIDR 대역의 IP 와 함께 사용할 수도 있습니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "cidr-l4-rule"
spec:
  endpointSelector:
    matchLabels:
      role: crawler
  egress:
  - toCIDR:
    - 192.0.2.0/24
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP

 

TLS 의 SNI (서버 이름 표시) 기능을 활용하여 제한할 수도 있습니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l4-sni-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  egress:
  - toPorts:
    - ports:
      - port: "443"
        protocol: TCP
      serverNames: # SNI
      - one.one.one.one

 

Application protocol Level

cilium 에서 아래에 해당하는 프로토콜 중 L7 정책을 통해 L7 어플리케이션 트래픽들을 제어할 수 있습니다.

 

// L7Rules is a union of port level rule types. Mixing of different port
// level rule types is disallowed, so exactly one of the following must be set.
// If none are specified, then no additional port level rules are applied.
type L7Rules struct {
        // HTTP specific rules.
        //
        // +optional
        HTTP []PortRuleHTTP `json:"http,omitempty"`

        // Kafka-specific rules.
        //
        // +optional
        Kafka []PortRuleKafka `json:"kafka,omitempty"`

        // DNS-specific rules.
        //
        // +optional
        DNS []PortRuleDNS `json:"dns,omitempty"`
}


3계층 및 4계층 정책과 달리 7계층 규칙 위반 시 패킷을 드랍하지 않고 애플리케이션 프로토콜별 접근 거부 메시지를 제공합니다. 예를 들어, HTTP 의 경우 403 Forbidden 을, DNS 의 경우, DNS 거부 응답을 보냅니다. 7 계층 정책은 Envoy 프록시를 통해 트래픽을 검사 후 처리를 진행합니다.

 


HTTP 정책의 경우 Path, Method, Hosts, Headers 를 정책의 구성요소로 사용합니다.

다음은 GET /public 만 허용하는 예제로 아래와 같이 구성할 수 있습니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "Allow HTTP GET /public from env=prod to app=service"
  endpointSelector:
    matchLabels:
      app: service
  ingress:
  - fromEndpoints:
    - matchLabels:
        env: prod
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "GET"
          path: "/public"

 

헤더가 설정된 경우에만 모든 GET /path1 및 PUT /path2 를 허용하는 예제입니다.

 

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l7-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  ingress:
  - toPorts:
    - ports:
      - port: '80'
        protocol: TCP
      rules:
        http:
        - method: GET
          path: "/path1$"
        - method: PUT
          path: "/path2$"
          headers:
          - 'X-My-Header: true'

 

DNS 정책은 DNS 트래픽에 적용되어 특정 DNS 쿼리 이름 또는 이름 패턴을 허용하거나 거부할 수 있습니다.

 

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: "tofqdn-dns-visibility"
spec:
  endpointSelector:
    matchLabels:
      any:org: alliance
  egress:
  - toEndpoints:
    - matchLabels:
       "k8s:io.kubernetes.pod.namespace": kube-system
       "k8s:k8s-app": kube-dns
    toPorts:
      - ports:
         - port: "53"
           protocol: ANY
        rules:
          dns:
            - matchName: "cilium.io"
            - matchPattern: "*.cilium.io"
            - matchPattern: "*.api.cilium.io"

  - toFQDNs:
      - matchName: "cilium.io"
      - matchName: "sub.cilium.io"
      - matchName: "service1.api.cilium.io"
      - matchPattern: "special*service.api.cilium.io"
    toPorts:
      - ports:
         - port: "80"
           protocol: TCP

 

참고로 쿠버네티스에서 DNS 정책을 적용할 때, service.namespace.svc.cluster.local에 대한 쿼리는 명시적으로 허용되어야 합니다. (matchPattern: *.*.svc.cluster.local.) 마찬가지로, FQDN을 완성하기 위해 DNS 검색 목록에 의존하는 쿼리는 전체가 허용되어야 합니다. 예를 들어, servicename로 성공하는 쿼리는 또는 servicename.namespace.svc.cluster.local.로 허용되어야 합니다.

 

Cilium NetworkPolicy

 

 

Cilium 은 표준 Kubernetes 의 Network Policy 보다 더 많은 기능을 제공합니다.

 

Cilium Network Policy 정책은 statefull 하며 응답 패킷은 자동으로 허용됩니다. 보안 정책은 수신 또는 송신 시점에 적용되어 네트워크 트래픽을 제어합니다.
정책이 로드되지 않은 경우 모든 통신을 기본으로 허용합니다. 하지만 첫 번째 정책 규칙이 로드되는 즉시 정책 적용이 자동으로 활성화되며, 이후부터는 허용 목록 방식으로 동작하여 명시적으로 허용되지 않은 패킷은 드랍됩니다. 마찬가지로 L4 정책이 적용되지 않은 엔드포인트는 모든 포트와의 통신이 허용되지만, 하나 이상의 L4 정책이 연결되면 명시적으로 허용하지 않는 한 모든 연결이 차단됩니다.

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] k8s / cilium 성능 테스트  (0) 2025.08.30
[cilium] Ingress / Gateway API  (0) 2025.08.23
[cilium] cluster mesh  (2) 2025.08.15
[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
반응형

 

cilium 이 k8s 플랫폼 내에서 동작을 하기 때문에 k8s 의 성능 테스트를 먼저 살펴보고, 그 다음 cilium 성능 테스트를 살펴보겠습니다.

지금까지는 Vagrant 로 VM 을 관리하면서 실습을 진행하였지만, 성능 테스트는 최대한 호스트 머신의 자원을 최대한 사용하는 것이 좋기 때문에 kind 로 스터디 실습을 진행하겠습니다.

 

실습 환경 구성

kind 클러스터를 생성할 때 kube-controller-manager, kube-scheduler, etcd, kube-proxy 의 메트릭 수집을 위해 bind-address를 설정합니다.

 

kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
EOF

 

kube-ops-view 설치

helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 \
  --set service.main.type=NodePort,service.main.ports.http.nodePort=30003 \
  --set env.TZ="Asia/Seoul" --namespace kube-system

 

metrics-server 설치

helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server \
  --set 'args[0]=--kubelet-insecure-tls' -n kube-system

 

prometheus-stack 설치

cat < monitor-values.yaml
prometheus:
  prometheusSpec:
    scrapeInterval: "15s"
    evaluationInterval: "15s"
    service:
      type: NodePort
      nodePort: 30001
grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  service:
    type: NodePort
    nodePort: 30002
alertmanager:
  enabled: false
defaultRules:
  create: false
prometheus-windows-exporter:
  prometheus:
    monitor:
      enabled: false
EOT

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --version 75.15.1 -f monitor-values.yaml --create-namespace --namespace monitoring

 

Kube-burner 를 활용한 성능 테스트

소개

Kube-burner 는 Golang 으로 작성된 쿠버네티스 성능 및 확장성 테스트 오케스트레이션 프레임워크입니다.

주요 특징은 다음과 같습니다.

  • 쿠버네티스 리소스를 대규모로 생성, 삭제, 조회, 패치 가능
  • Prometheus 메트릭 수집 및 인덱싱 지원
  • 다양한 측정(Measurements) 기능 제공
  • 알림(Alerting) 기능 제공
  • Kubernetes 공식 클라이언트 라이브러리인 client-go를 광범위하게 활용

설치

git clone https://github.com/kube-burner/kube-burner.git
cd kube-burner

# 바이너리 설치(추천) <mac M칩>
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-darwin-arm64.tar.gz # mac M
tar -xvf kube-burner-V1.17.3-darwin-arm64.tar.gz

sudo cp kube-burner /usr/local/bin

 

시나리오 1: 기본 배포 테스트

deployment 1개(pod 1개)를 생성하고 삭제하면서 jobIterations, qps, burst 의 의미를 확인합니다.

 

cat << EOF > s1-config.yaml
global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 1  # How many times to execute the job , 해당 job을 5번 반복 실행
    qps: 1            # Limit object creation queries per second , 	초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
    burst: 1          # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml
        replicas: 1
EOF

#
cat << EOF > s1-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-{{ .Iteration}}-{{.Replica}}
  labels:
    app: test-{{ .Iteration }}-{{.Replica}}
    kube-burner-job: delete-me
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-{{ .Iteration}}-{{.Replica}}
  template:
    metadata:
      labels:
        app: test-{{ .Iteration}}-{{.Replica}}
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
EOF

 

테스트 시 중요한 주요 필드는 다음과 같습니다.

 

jobIterations 작업 반복 횟수
qps 초당 쿼리 수(Query Per Second)
burst 순간 최대 처리량

 

아래 명령어로 테스트를 해보겠습니다.

kube-burner init -c s1-config.yaml --log-level debug

 

preLoadImages: false

초기에 이미지 다운로드하지 않고, 바로 이미지를 컨테이너화하여 업로드합니다.

waitWhenFinished: false
테스트 구성대로 잘 만들어졌는지 확인하지 않고, 바로 종료합니다.

JobIterations: 5

Job 에 정의된 행위들이 반복되어 테스트를 수행합니다.

 

 

objects.replicas: 2

replica 갯수를 늘려 테스트를 수행합니다.

 

 

replica 갯수를 2로 증가시켜 테스트하면 10 개의 파드가 생성된 것을 확인할 수 있습니다.

jobIterations: 10

다시 job 의 반복 회수를 10 개로 증가시켜 qps 와 burst 의 의미를 파악해보겠습니다. 현재 qps 가 1 이어서 API Controller 에 1초에 1개의 요청만 할 수 있습니다. 1초에 1개씩 pod 가 생성되어 10초가 걸렸습니다.

jobIterations: 10, qps: 1, burst:10, objects.replicas: 1

jobIterations: 100, qps: 1, burst:100, objects.replicas: 1

두 가지 케이스 모두 qps 가 1 이지만 burst(순간 처리할 수 있는 요청 최대치) 가 jobIterations 와 같아 1초만에 수행할 수 있습니다.

jobIterations: 10, qps: 1, burst:20, objects.replicas: 2

2개의 레플리카를 10번에 걸쳐 생성하므로 20 개의 오브젝트를 생성 요청하는데 burst 가 20 이므로 1초에 생성됩니다.

jobIterations: 10, qps: 1, burst:10, objects.replicas: 2

마찬가지로, 2개의 레플리카를 10번에 걸쳐 생성하므로 20 개의 오브젝트를 생성 요청하는데 10 개의 bust 만 쓸 수 있으므로 1초씩 진행됩니다.
jobIterations: 20, qps: 2, burst:20, objects.replicas: 2

같은 원리로 처음에 조건이 부합하여 한 번에 처리가 진행이 되다가 슬슬 처리가 늦어지는 것을 알 수 있습니다.

 

시나리오 2: 노드 1대에 최대 파드 배포 (150개)

jobIterations: 100, qps: 300, burst: 300, replicas: 1로 설정하여 최대 150개 파드 배포 테스트 해보겠습니다.

 

 

5개의 노드가 아직 pending 상태로 남아있습니다. 원인 파악을 위해 99번 파드에 대해 자세히 살펴보겠습니다.

 

 

"Too many pods. preemption: 0/1 nodes are availale:" 라는 메세지를 볼 수 있고, node 의 상태를 확인해보니

 

 

모든 가용영역을 다 사용한 것을 볼 수 있습니다.

기본적으로 쿠버네티스는 노드당 110개의 파드만 허용합니다. 더 많은 파드를 배포하려면 다음과 같이 설정을 변경해야 합니다.

 

# 현재 설정 확인
kubectl get cm -n kube-system kubelet-config -o yaml

# maxPods 설정 변경
docker exec -it myk8s-control-plane bash
cat /var/lib/kubelet/config.yaml
apt update && apt install vim -y
vim /var/lib/kubelet/config.yaml
# maxPods: 150 추가
systemctl restart kubelet
systemctl status kubelet
exit

 

시나리오 3: 파드 300개 배포 시도

jobIterations: 300, qps: 300, burst: 300 objects.replicas: 1로 설정하여 최대 300개 파드 배포 테스트를 해보겠습니다.

파드 배포하기 전, 넉넉하게 400 개의 maxPod 를 설정하고 테스트를 수행합니다.

 

# maxPods 설정 변경
docker exec -it myk8s-control-plane bash
cat /var/lib/kubelet/config.yaml
apt update && apt install vim -y
vim /var/lib/kubelet/config.yaml
# maxPods: 400 추가
systemctl restart kubelet
systemctl status kubelet
exit

 

 

maxPod 를 최대치까지 늘렸는데 containerCreating 된 상태의 pod 들이 존재합니다. pod 로그들을 통해 원인을 파악해보면,

 

 

배포 시에 PodCIDR 대역 문제가 발생하는 것을 볼 수 있습니다.("no IP addresses available in range set") 현재 PodCIDR 이 /24 대역(252개 IP)이므로 300개 파드를 생성할 수 없습니다.

 

K8S Performance

최대 수용 규모와 내부 구조

쿠버네티스의 퍼포먼스를 이해하려면, 쿠버네티스의 내부 구조와 처리 절차들을 이해하고 있어야 합니다.

먼저 쿠버네티스 v1.33 기준 단일 클러스터 최대 수용 규모와 절차들은 다음과 같습니다.

  • 노드 수: 5,000대 이하
  • 노드당 파드 수: 110개 이하
  • 총 파드 수: 150,000개 이하
  • 총 컨테이너 수: 300,000개 이하

Kubernetes는 실제 사용자의 워크로드를 실행하는 Data Plane과 클러스터 전체를 관리하는 Control Plane으로 구성됩니다. Control Plane은 주로 kube-apiserver와 etcd로 구성되며, 이 두 컴포넌트에서 문제가 발생하면 리소스 관리에 심각한 영향을 미칩니다.

 

출처 : https://tech.kakaopay.com/post/jack-k8s-internals-part-2/

 

 

kube-apiserver

  • HTTP(S) 기반의 RESTful API 서버
  • YAML, JSON, Protobuf 형식으로 외부 클라이언트(kube-controller-manager, kubelet, kubectl 등)와 통신
  • 클러스터상 리소스 관리를 위한 최소한의 기능을 제공
  • 복잡한 로직(스케줄링, 네트워킹, Progressive Delivery 등)은 외부 컴포넌트에 위임하는 MSA 구조

etcd

  • gRPC로 kube-apiserver와 통신하는 Key-Value 데이터베이스
  • RAFT 알고리즘을 사용하여 리더 선출 및 Write-Ahead Logging(WAL) 방식으로 데이터 복제
  • Split-brain 등의 문제를 처리하는 CAP 정리의 CP/EC 시스템
  • 데이터 일관성이 매우 높지만 성능 상 한계 존재

K8S API 성능 분석

응답 시간 분석

Pod 개수에 따른 API 응답 시간을 측정한 결과 다음과 같습니다.

 

100개 Pod 조회

curl -o /dev/null -s -w 'Total: %{time_total}s\n' 127.0.0.1:8001/api/v1/pods?limit=100
# Result: Total: 0.031002s (31ms)

 

10,000개 Pod 조회

curl -o /dev/null -s -w 'Total: %{time_total}s\n' 127.0.0.1:8001/api/v1/pods?limit=10000
# Result: Total: 2.131027s (2,131ms - 약 70배 증가)

 

계산 결과, 리소스 한 개당 처리 시간은 약 0.21ms 씩 증가합니다. 데이터 전송량도 비례적으로 증가합니다:

  • 100개 Pod: 약 0.45MB
  • 10,000개 Pod: 약 44.4MB

응답 속도가 조금 늦긴 하지만 쿠버네티스의 안전성의 비용으로 생각한다면 그리 속도가 느린 것은 아닌 것 같다. 하지만 메모리의 사용량이 문제이다.

 

메모리 사용량 분석

etcd 는 요청 처리 시 메모리에 모든 데이터를 적재한 후 응답을 반환합니다. 

 

# 프로세스 시작 후 안정화 상태
08:28:48    0    7    0.00    0.00   10.7G  110.0M   0.7%   etcd

# 첫 번째 curl 요청 (메모리 사용량 60MB 증가)
08:29:24    0    7  6941.58    0.00   10.8G  236.7M   1.5%   etcd

# 두 번째 curl 요청 (메모리 사용량 30MB 증가)  
08:29:28    0    7  1162.00    0.00   10.8G  265.0M   1.7%   etcd

# etcd GC 실행 후 (메모리 사용량 90MB 감소)
08:29:36    0    7  9306.00    0.00   10.9G  187.1M   1.2%   etcd

 

요청 하나를 처리하는데 대략 30~60MB 정도의 메모리 공간이 필요하며 10,000 개의 Pod Resource를 Protobuf로 저장했을 때 필요한 저장공간 (35MB)과 거의 유사합니다.

kube-apiserver 의 경우, 더 복잡한 처리 과정을 거칩니다:

  1. etcd로부터 protobuf 포맷으로 데이터 수신
  2. Go struct로 역직렬화(deserialization)
  3. JSON 형태로 직렬화(serialization)

과정이 최소한으로 필요하고, 필요에 따라 버전 변환 및 타입 변환을 하기 때문에

 

# 프로세스 시작 후 안정화 상태
08:41:44    0    7     0.00    0.00    1.4G  691.4M   4.3%   kube-apiserver

# 첫 번째 curl 요청 (메모리 사용량 120MB 증가)
08:41:49    0    7  9304.00    0.00    1.4G  750.7M   4.7%   kube-apiserver
08:41:50    0    7   346.00    0.00    1.5G  815.9M   5.1%   kube-apiserver

...

# 여덟번째 curl 127.0.0.1:8001/api/v1/pods?limit=10000 (메모리 사용량 유지)
08:42:19        0         7   1137.00      0.00    2.0G    1.1G   6.9%  kube-apiserver
08:42:20        0         7      0.00      0.00    2.0G    1.1G   6.9%  kube-apiserver
08:42:21        0         7      0.00      0.00    2.0G    1.1G   6.9%  kube-apiserver
08:42:22        0         7      0.00      0.00    2.0G    1.1G   6.9%  kube-apiserver

 

매 요청마다 약 100MB 내외의 메모리가 필요하며, 이는 JSON 데이터 크기의 약 2.5배에 해당합니다.

만약 100개의 동시 요청을 처리할 경우, kube-apiserver 메모리 용량이 6배인 6GB 까지 치솟고, cgroup 으로 설정한 hard limit 을 넘어가 OOM Kill 이 발생하게 됩니다.

 

# 30초 만에 메모리 사용량이 6GB까지 증가하여 OOM Kill 발생
01:52:34    0    7   118.00    0.00    1.4G  693.9M   4.3%   kube-apiserver
01:52:55    0    7 36775.00    0.00    7.6G    6.0G  38.2%   kube-apiserver
01:52:56    0    7  5954.00    0.00    0.0k    0.0k   0.0%   kube-apiserver  # OOM Killed

 

동일한 시간 동안 etcd의 메모리 사용량도 30배(6GB) 증가합니다.

 

01:52:30    0    7     0.00    0.00   10.8G  165.4M   1.0%   etcd
01:52:45    0    7 14475.00    0.00   16.6G    6.0G  38.1%   etcd

 

 

문제 발생 원인 분석

etcd 는 데이터 일관성을 위해 Range 요청 처리 중 Store 에 Lock을 걸고 해당 데이터를 복제합니다. 이 과정에서 대량의 메모리가 필요하며, Zero Copy 로 구현할 경우 성능 저하가 심각합니다. 심지어 etcd 관련 api 제약사항이 아래와 같습니다.

  • etcd 에는 Range API 만 존재하여 대용량 데이터를 한 번에 처리해야 함
  • kube-apiserver 도 이를 처리할 대안이 제한적
  • 2초간 100건의 동일한 요청이 들어오면 메모리 급증 불가피

문제 발생 조건들을 살펴보면 한 번에 조회 가능한 리소스 개수가 매우 많고, 해당 리소스를 조회하는 요청이 매우 많았습니다.

kubelet 과 같은 컨트롤러가 특정 노드의 Pod 만 조회하려 할 때,

 

/api/v1/pods?fieldSelector=spec.nodeName=worker-node-10

 

kube-apiserver는 spec.nodeName에 대한 인덱싱이 없어 etcd에서 모든 Pod를 검색해야 하므로, 결국 전체 데이터를 로드하게 됩니다.

 

해결 방안

1. API Limit/Continue 활용

limit 과 continue 를 활용해서 한 번에 조회하는 양을 제한하여 메모리 사용량을 덜 사용합니다.

 

# kubectl의 실제 요청 예시
kubectl get po -v6
# GET /api/v1/namespaces/default/pods?limit=500
# GET /api/v1/namespaces/default/pods?continue=eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJydiI6MzkzNDcsInN0YXJ0IjoidGVzdC01NzQ2ZDRjNTlmLTJuNTUyXHUwMDAwIn0&limit=500

 

2. ResourceVersion/ResourceVersionMatch 활용

Strong Consistency 가 필요하지 않은 경우 etcd 에 요청하지 않고 api server 에 존재하는 캐시된 데이터를 사용합니다.

 

# resourceVersion="0"으로 요청하면 kube-apiserver 캐시에서 데이터 반환
curl "127.0.0.1:8001/api/v1/pods?resourceVersion=0"

 

이 방법을 사용하면 etcd 부하는 완전히 제거되지만, kube-apiserver 의 OOM은 여전히 발생할 수 있습니다.

 

3. API Priority and Fairness(APF) 활용

요청을 분류하여 Rate Limit 을 적용하는 방법도 있습니다.

 

apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: FlowSchema
metadata:
  name: cilium-pods
spec:
  distinguisherMethod:
    type: ByUser
  matchingPrecedence: 1000
  priorityLevelConfiguration:
    name: cilium-pods
  rules:
  - resourceRules:
    - apiGroups:
      - "cilium.io"
      clusterScope: true
      resources:
      - "*"
      verbs:
      - "list"
---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: PriorityLevelConfiguration
metadata:
  name: cilium-pods
spec:
  type: Limited
  limited:
    nominalConcurrencyShares: 5
    limitResponse:
      queuing:
        handSize: 4
        queueLengthLimit: 50
        queues: 16
        type: Queue

 

운영 관점의 해결법

  • 불필요한 리소스 지속적 삭제
  • 적절한 리소스 개수 유지
  • 모니터링을 통한 사전 예방

 

시나리오 4: api-intensive - 파드를 생성(configmap, secret) 후 삭제

kube-burner 의 예제 샘플인 examples/workloads/api-intensive 를 조금 수정해서 api-server 의 과부화를 유도해보겠습니다.

 

cat << EOF > api-intensive-100.yml
jobs:
  - name: api-intensive
    jobIterations: 100
    qps: 100
    burst: 100
    namespacedIterations: true
    namespace: api-intensive
    podWait: false
    cleanup: true
    waitWhenFinished: true
    preLoadImages: false # true
    objects:
      - objectTemplate: templates/deployment.yaml
        replicas: 1
      - objectTemplate: templates/configmap.yaml
        replicas: 1
      - objectTemplate: templates/secret.yaml
        replicas: 1
      - objectTemplate: templates/service.yaml
        replicas: 1

  - name: api-intensive-patch
    jobType: patch
    jobIterations: 10
    qps: 100
    burst: 100
    objects:
      - kind: Deployment
        objectTemplate: templates/deployment_patch_add_label.json
        labelSelector: {kube-burner-job: api-intensive}
        patchType: "application/json-patch+json"
        apiVersion: apps/v1
      - kind: Deployment
        objectTemplate: templates/deployment_patch_add_pod_2.yaml
        labelSelector: {kube-burner-job: api-intensive}
        patchType: "application/apply-patch+yaml"
        apiVersion: apps/v1
      - kind: Deployment
        objectTemplate: templates/deployment_patch_add_label.yaml
        labelSelector: {kube-burner-job: api-intensive}
        patchType: "application/strategic-merge-patch+json"
        apiVersion: apps/v1

  - name: api-intensive-remove
    qps: 500
    burst: 500
    jobType: delete
    waitForDeletion: true
    objects:
      - kind: Deployment
        labelSelector: {kube-burner-job: api-intensive}
        apiVersion: apps/v1

  - name: ensure-pods-removal
    qps: 100
    burst: 100
    jobType: delete
    waitForDeletion: true
    objects:
      - kind: Pod
        labelSelector: {kube-burner-job: api-intensive}

  - name: remove-services
    qps: 100
    burst: 100
    jobType: delete
    waitForDeletion: true
    objects:
      - kind: Service
        labelSelector: {kube-burner-job: api-intensive}

  - name: remove-configmaps-secrets
    qps: 100
    burst: 100
    jobType: delete
    objects:
      - kind: ConfigMap
        labelSelector: {kube-burner-job: api-intensive}
      - kind: Secret
        labelSelector: {kube-burner-job: api-intensive}

  - name: remove-namespace
    qps: 100
    burst: 100
    jobType: delete
    waitForDeletion: true
    objects:
      - kind: Namespace
        labelSelector: {kube-burner-job: api-intensive}
EOF

# 수행
kube-burner init -c api-intensive-100.yml --log-level debug

 

100 개의 네임스페이스에 각각 Deployment, ConfigMap, Secret, Service 를 QPS 100 으로 대량 생성하고 생성된 리소스들을 패치하여 API 서버의 UPDATE 성능을 측정합니다. 이후 모든 리소스를 순차적으로 삭제하여 DELETE API 성능까지 종합적으로 테스트하는 예제입니다.

 

모니터링

다른 클라이언트로부터 받는 kube-apiserver 요청의 개수를 모니터링하고 싶다면, API 서버 QPS를 리소스별, 요청 타입별, 응답 코드별 모니터링을 진행하면 됩니다.

 

최근 5분간 Kubernetes API 서버가 처리한 요청에 대한 집계

 

# 응답 코드별 집계
sum by(verb) (irate(apiserver_request_total{job="apiserver"}[5m]))
# 리소스, 응답 코드, verb 별 집계
sum by(resource, code, verb) (irate(apiserver_request_total{job="apiserver"}[5m]))

# 최종
sum by(resource, code, verb) (rate(apiserver_request_total{resource=~".+"}[5m]))
or
sum by(resource, code, verb) (irate(apiserver_request_total{resource=~".+"}[5m]))

 

Cilium Performance

cilium 의 퍼포먼스를 이해하려면, cilium 의 내부 구조와 처리 절차들을 이해하고 있어야 합니다.

Cilium 내부 구조

 

cilium 개발자들은 kube-apiserver 나 clustermesh 에서 이벤트를 받아 ebpf 프로그램으로 처리하는데, 각 구간마다 장애가 발생한다면 어떻게 처리해야 할지 고민이 되었습니다.

 

 

이벤트가 적절한지 검증이 필요했고, 장애가 발생할 경우 재시도 로직이 필요했고, 상태를 저장할 필요가 있었습니다.

 

 

stateDB 와 효율적인 업데이트를 위한 Reconciler 컴포넌트를 중간에 두어 처리할 수 있게 하였습니다.

 

 

그래서 위와 같이 상태를 저장하는 StateDB 와 안정적인 운영을 위한 reconciler 컴포넌트(재시도 로직 및 헬스, 메트릭 체크) 를 도입하게 되었습니다.

Cilium 실습 환경 구성

kind 를 활용하여 cilium 테스트를 위한 실습 환경을 구성해보겠습니다.

 

kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  kubeadmConfigPatches: # Prometheus Target connection refused bind-address 설정
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
networking:
  disableDefaultCNI: true
  kubeProxyMode: none
  podSubnet: "10.244.0.0/16"   # cluster-cidr
kubeadmConfigPatches:
- |
  kind: ClusterConfiguration
  controllerManager:
    extraArgs:
      allocate-node-cidrs: "true"
      cluster-cidr: "10.244.0.0/16"
      node-cidr-mask-size: "22"
EOF

cilium install --version 1.18.1 --set ipam.mode=kubernetes --set ipv4NativeRoutingCIDR=172.20.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=true --set endpointRoutes.enabled=true --set directRoutingSkipUnreachable=true \
--set kubeProxyReplacement=true --set bpf.masquerade=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set hubble.enabled=true --set hubble.relay.enabled=true --set hubble.ui.enabled=true \
--set hubble.ui.service.type=NodePort --set hubble.ui.service.nodePort=30003 \
--set prometheus.enabled=true --set operator.prometheus.enabled=true --set envoy.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload\,traffic_direction}" \
--set debug.enabled=true  # --dry-run-helm-values

# metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes/addons/prometheus/monitoring-example.yaml

kubectl patch svc -n cilium-monitoring prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n cilium-monitoring grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'

 

cilium 을 설치하기 위해 disableDefaultCNI: true 옵션과 kubeProxyMode: none 옵션을 사용해서 CNI 없이 클러스터 구성을 하고 이후 cilium CNI 플러그인을 설치해서 네트워킹을 활성화합니다. 모니터링을 위한 prometheus 와 grafana 도 설치합니다.

 

참고로 control-plane 을 3개를 띄우면 HA Proxy 를 구성할 수 있다고 합니다.

 

# Prometheus Target connection refused bind-address 설정 : kube-controller-manager , kube-scheduler , etcd , kube-proxy
kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: control-plane
- role: control-plane
networking:
  apiServerAddress: "127.0.0.1"
  apiServerPort: 6443
  disableDefaultCNI: true
  kubeProxyMode: none
EOF

#
docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED          STATUS          PORTS                       NAMES
3e0a95aff7a6   kindest/haproxy:v20230606-42a2262b   "haproxy -W -db -f /…"   12 minutes ago   Up 12 minutes   127.0.0.1:6443->6443/tcp    myk8s-external-load-balancer
981cf7c18ac6   kindest/node:v1.33.2                 "/usr/local/bin/entr…"   12 minutes ago   Up 12 minutes   127.0.0.1:49776->6443/tcp   myk8s-control-plane2
40b5b2ebe5bf   kindest/node:v1.33.2                 "/usr/local/bin/entr…"   12 minutes ago   Up 12 minutes   127.0.0.1:49775->6443/tcp   myk8s-control-plane
6aa3d5b5b1ab   kindest/node:v1.33.2                 "/usr/local/bin/entr…"   12 minutes ago   Up 12 minutes   127.0.0.1:49777->6443/tcp   myk8s-control-plane3

 

Cilium 테스트

cilium 에서는 네트워크 관련 성능 및 기능 테스트 도구를 제공합니다. cilium connectivity -h 명령어로 테스트 관련 명령어를 확인할 수 있습니다.

 

cilium connectivity -h
Connectivity troubleshooting
Available Commands:
  perf        Test network performance
  test        Validate connectivity in cluster

 

cilium connectivity test --debug  
🐛 [cilium-test-1] Registered connectivity tests
🐛   <Test no-policies, 8 scenarios, 0 resources, expectFunc <nil>>
🐛   <Test no-policies-from-outside, 1 scenarios, 0 resources, expectFunc <nil>>
🐛   <Test no-policies-extra, 2 scenarios, 0 resources, expectFunc <nil>>
...

 

test 명령어 같은 경우에는 네트워크 정책이 없을 때 outside 로 통신이 되는지 여부, 모든 ingress deny 하고 나서 통신이 되는지 여부 등 통신 pass / fail 의 점검을 할 수 있습니다. perf 명령어는 같은 노드 내부에서 통신 성능 테스트를 하거나, host to pod 경유 흐름에서 통신 성능 테스트를 수행합니다.

 

iperf3 을 활용해서 대역폭을 측정할 수도 있습니다.

 

# TCP 양방향 테스트
kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -t 5 --bidir

 

결과 예시:

  • Client→Server (TX): 53.6 Gbps
  • Server→Client (RX): 39.9 Gbps
  • 재전송: TX=11, RX=14 (일부 패킷 손실)
  • 전체적으로 40-54Gbps 대역폭 측정

 

Cilium BPF Map 모니터링

Map Operations 분석

 

# 상위 5개 BPF Map 연산 확인
topk(5, avg(rate(cilium_bpf_map_ops_total{k8s_app="cilium", pod=~"$pod"}[5m])) 
  by (pod, map_name, operation))

 

cilium_bpf_map_ops_total 메트릭으로 다음을 확인할 수 있습니다:

  • BPF 맵 조작 횟수 (누적 카운터)
  • Pod별, 맵별, 연산별 평균 발생률
  • 어떤 BPF 맵에서 가장 많은 연산이 발생하는지 파악

 

eBPF Maps 최적화

mapDynamicSizeRatio 설정

모든 eBPF 맵은 용량 제한이 설정되어 있으며, Cilium은 시스템 전체 메모리 비중을 기준으로 자동 계산된 기본값을 사용합니다.

# eBPF 맵 용량을 시스템 메모리의 1%로 설정
mapDynamicSizeRatio: 0.01

 

Cilium 튜닝

Cilium Endpoint Slices (CES) Beta

k8s 에서 endpoint 와 endpoint slice 의 개념처럼 cilium 에서도 watch 할 endpoint 들을 별도로 구성하여 제공하는 기능입니다.

 

 

  기존 endpoint cilium endpoint
watch 대상 파드 별 endpoint CiliumEndpointSlice CRD 에 정의된 파드
agent watch 대상 모든 에이전트가 모든 endpoint 를 watch 상당히 적은 watch event
룰 매칭 기준 IP label, identities

 

기존 환경에서는 5,000 노드 × 100 엔드포인트 였다면 500,000 개를 Watch 해야 해서 kube-api 서버와 etcd 에 과도한 부하가 갔지만, CES 에서는 특정 조건에 부합하는 엔드포인트를 지정하여 리소스를 관리할 수 있습니다.

 

helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set ciliumEndpointSlice.enabled=true
kubectl rollout restart -n kube-system deployment cilium-operator
kubectl rollout restart -n kube-system ds/cilium

 

ciliumEndpointSlice.enabled=true 옵션으로 CES 를 사용할 수 있으며, kubectl get ciliumendpointslices.cilium.io -A 명령어로 watch 대상을 확인할 수 있습니다. 해당 옵션의 활성화 여부에 따라 차이가 많이 나는 것을 확인할 수 있습니다.

 

eBPF Host Routing , Netkit

 

 

cilium 은 ebpf 를 통해 네트워크 스택을 우회하고 host routing 을 지원하고 있습니다. ingress 의 경우 cpu 의 큐잉을 거치지 않지만, egress 의 경우 어쩔 수 없이 cpu 의 큐잉을 사용하고 있습니다.

 

 

netkit 디바이스를 사용하면 veth 가 쌍으로 존재해 egress 의 경우에도 ebpf 로 처리할 수 있어 컨테이너 네트워크의 오버헤드가 사라졌다라고 홍보하고 있습니다. (?)

 

helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set bpf.datapathMode=netkit

kubectl rollout restart -n kube-system deployment cilium-operator
kubectl rollout restart -n kube-system ds/cilium

 

위 명령어로 netkit 을 사용할 수 있지만 kernel 이 6.7 이상이어야 하고, CONFIG_NETKIT 옵션 활성화가 필요합니다.

 

cilium 성능 모니터링 메트릭

Cilium 상태 확인

# Cilium 상태 점검
cilium status

# BPF 맵 상태 확인
cilium bpf map list

# 메트릭 엔드포인트 확인
cilium metrics

 

주요 성능 메트릭

# BPF Map 연산 모니터링
cilium_bpf_map_ops_total

# 데이터패스 패킷 처리
cilium_datapath_packets_total

# 정책 계산 시간
cilium_policy_regeneration_time_stats_seconds

# 엔드포인트 상태 변화
cilium_endpoint_state_count

 

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] Security  (0) 2025.09.07
[cilium] Ingress / Gateway API  (0) 2025.08.23
[cilium] cluster mesh  (2) 2025.08.15
[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
반응형
요약 : 이번 편은 ingress / gateway API 는 일반적인 Kubernetes 의 과정과 유사합니다.
cilium 에서 해당 리소스를 ebpf 로 처리해준다는 내용입니다.
Service Mesh (서비스 메시) 란?

분산 애플리케이션 환경은 다양한 마이크로 애플리케이션들이 서로 데이터를 공유하고 클라우드와 온프레미스 경계를 넘나들며 네트워크 통신을 한다. 전통적인 IP 기반 통신을 넘어서 애플리케이션 프로토콜까지 이해해야만 진정한 가시성을 확보할 수 있게 되었다. 서비스 메시는 이러한 환경에서 관측가능성(observability)을 지원하는 인프라스트럭처다. 참고로 네트워크가 메시(mesh) 형태로 구성되어 있어 'mesh' 라고 명명되었다고 한다.

- L7 트래픽 (HTTP, REST, gRPC, WebSocket 등) 을 인식할 수 있어야 한다.
- 네트워크 식별자에 의존하지 않고, 신원을 기반으로 하여 서로를 인증할 수 있다.
- tracing 이나 metric 형태로 어플리케이션 모니터링(관측 가능성)이 가능해야 한다.
- 어플리케이션 코드 변경 없이 사용 가능해야 한다.

 

cilium 에서는 service mesh 도 자체적으로 지원합니다.

 

 

(좌측) 전통적인 서비스 메시의 경우(ambient 모드 X) envoy 사이드카 패턴으로 한 번의 네트워크 스택을 거쳐야 했습니다.

그림을 자세히 보면 envoy 로 트래픽을 가로채서 다시 application 으로 트래픽을 보낸다. 하지만 (우측) cilium 은 ambient 모드와 유사하게 노드마다 공통의 cilium envoy 를 두고 트래픽을 처리합니다. L3/L4 트래픽의 경우, cilium 의 eBPF 로 직접 처리하지만 L7 트래픽은 공통의 envoy 가 처리 후 socket 레벨 단으로 바로 전송합니다.

 

더 자세하게 실제로 구현한 내용을 보면,

보통 트래픽을 가로채기할 경우 iptables 의 REDIRECT 방법을 이용하긴 하지만, 패킷을 직접 수정하여 목적지 IP 주소가 변경되는 이슈가 존재합니다. 원래 목적지 주소를 유지한 채 패킷을 transparent 하게 전달하려면(NAT 처리하지 않고) TPROXY 를 사용합니다. cilium 은 트래픽이 도착하면 ebpf 코드가 먼저 처리하고 TPROXY 커널 기능을 사용하여 Envoy 로 투명하게 전달합니다.

출처 : https://docs.kernel.org/networking/tproxy.html#redirecting-traffic

 

 kubernetes ingress support, gateway api support, GAMMA support 세 가지 방법을 지원하고 있습니다.

 

Kubernetes Ingress Support

Cilium Ingress 는 Kubernetes Ingress 의 기능을 서포트하며 dedicated 모드와 shared 모드를 제공합니다. 모드를 변경할 수 있으나, 새로운 로드밸런서의 ip 를 부여받기 때문에 ingress backend 와 활성화 되어 있는 연결이 종료될 수 있습니다.

 

모드 설명
dedicated 각 Ingress 리소스마다 별도의 LoadBalancer 를 생성
shared 클러스터 내 모든 Ingress 리소스가 하나의 LoadBalancer 를 공유

 

ingress 모드를 사용하려면 nodePort.enabled=true 옵션을 통해 nodePort 를 활성화하고, kubeProxyReplacement=true 옵션을 통해 kube-proxy 를 교체하여 활성화해야 합니다. 만약 L7 Proxy 를 사용하고자 한다면 l7Proxy=true 옵션을 활성화해야 합니다. (default 옵션)

 

 

참고로 cilium network policy 가 적용될 때는 ingress 서비스에 들어오기 전과 후 두 번 평가를 하게 됩니다.

 

설치

# helm 으로 설치 시 아래 옵션 활성화
--set ingressController.enabled=true
--set ingressController.loadbalancerMode=shared
   
# deploy, daemonset 활성화
$ kubectl -n kube-system rollout restart deployment/cilium-operator
$ kubectl -n kube-system rollout restart ds/cilium

 

설치 확인은 cilium config view 로 간단히 확인할 수 있습니다.

 

cilium config view | grep -E '^loadbalancer|l7'

 

실습

간단하게, external IP 를 발급 받고 외부에서 접근 가능하도록 L2 Announcement 를 사용하겠습니다.

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2" 
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  blocks:
  - start: "192.168.10.211"
    stop:  "192.168.10.215"
EOF

 

 

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  interfaces:
  - eth1
  externalIPs: true
  loadBalancerIPs: true
EOF

 

 

192.168.10.211 IP 를 부여받고, k8s-w1 노드가 리더노드가 되었습니다.

아래 istio 실습 예제를 사용해보면서 cilium ingress support 와 어떻게 다른지 살펴보겠습니다.

 

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.26/samples/bookinfo/platform/kube/bookinfo.yaml

 

 

다른 서비스 메시들과 다르게 사이드카 컨테이너가 생성되지 않고, ambient 모드와 유사해 보입니다.

이제 ingress 를 생성하여 외부에 노출해보겠습니다.

 

 

cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: basic-ingress
  namespace: default
spec:
  ingressClassName: cilium
  rules:
  - http:
      paths:
      - backend:
          service:
            name: details
            port:
              number: 9080
        path: /details
        pathType: Prefix
      - backend:
          service:
            name: productpage
            port:
              number: 9080
        path: /
        pathType: Prefix
EOF

 

"/" 경로로 올 경우 productpage 서비스로, "/details" 경로로 올 경우 details 서비스로 가도록 ingress 설정하였습니다.

참고로, 생성된 ingress 를 확인해보면 ingress support 를 shared 모드로 설정해두었기 때문에 external-ip 가 ingress ip 와 같다는 것을 볼 수 있습니다.

 

 

해당 경로들로 통신을 해보겠습니다. 먼저 내부에서 접근해보겠습니다.

 

LBIP=$(kubectl get svc -n kube-system cilium-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $LBIP

# / 로 통신 -> 성공
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/
# /details 로 통신 -> 성공
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/details/1
# /ratings 로 통신 -> 실패
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/ratings

 

 

정상적으로 ingress 정책에 따라 통신이 되고 있습니다. -v 옵션을 주어서 자세히 보면 envoy 서버를 경유해서 x-envoy-upstream-service-time 이라는 특정 헤더가 세팅되는 것을 확인할 수 있습니다. (외부에서 접근하면 x-forwarded-for 헤더로 source-ip 를 기재하게 됩니다.)

 

다른 ingress 와 공존 여부

공존은 할 수 있으나 cilium-ingress 를 사용하지는 않습니다.

 

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --create-namespace -n ingress-nginx

 

 

helm 으로 nginx ingress 를 설치하면 별도의 nginx ingress 가 생성됩니다. 이후 ingress 리소스를 직접 생성하고 통신해보면,

 

cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webpod-ingress-nginx
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.webpod.local
    http:
      paths:
      - backend:
          service:
            name: webpod
            port:
              number: 80
        path: /
        pathType: Prefix
EOF

 

 

nginx.webpod.local 에 의해 트래픽이 전달되는 것을 확인할 수 있습니다.

 

dedicated mode

지금까지 shared 모드를 살펴보았으니 dedicated 모드를 살펴보겠습니다.

 

cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webpod-ingress
  namespace: default
  annotations:
    ingress.cilium.io/loadbalancer-mode: dedicated
spec:
  ingressClassName: cilium
  rules:
  - http:
      paths:
      - backend:
          service:
            name: webpod
            port:
              number: 80
        path: /
        pathType: Prefix
EOF

 

현재 전역적으로 로드밸런서 모드를 shared 모드로 사용하고 있어, ingress.cilium.io/loadbalancer-mode: dedicated 애너테이션을 통해 dedicated 모드로 로드밸런서를 생성합니다.

 

 

cilium  클래스로 igress ip 가 하나 더 생긴 것을 볼 수 있습니다. 위에서 마찬가지로 접속하는 클라이언트의 ip 를 x-forwarded-for 헤더에 담아서 통신하게 됩니다.

 

ingress path type

cilium ingress path type 은 Exact, Prefix, ImplementationSpecific 세 가지를 지원합니다.

Exact 는 정확히 일치할 경우, Prefix 는 접두사처럼 앞에만 일치하는 경우, ImplementationSpecific 는 IngressClass에 따라 달라집니다. 별도 pathType 으로 처리하거나, Prefix 또는 Exact 경로 유형과 같이 동일하게 처리할 수 있습니다.

 

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/main/examples/kubernetes/servicemesh/ingress-path-types.yaml
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/main/examples/kubernetes/servicemesh/ingress-path-types-ingress.yaml

 

적용하면, 아래와 같이 스펙이 결정됩니다.

 

kc get ingress multiple-path-types -o yaml
spec:
  ingressClassName: cilium
  rules:
  - host: pathtypes.example.com
    http:
      paths:
      - backend:
          service:
            name: exactpath
            port:
              number: 80
        path: /exact
        pathType: Exact
      - backend:
          service:
            name: prefixpath
            port:
              number: 80
        path: /
        pathType: Prefix
      - backend:
          service:
            name: prefixpath2
            port:
              number: 80
        path: /prefix
        pathType: Prefix
      - backend:
          service:
            name: implpath
            port:
              number: 80
        path: /impl
        pathType: ImplementationSpecific
      - backend:
          service:
            name: implpath2
            port:
              number: 80
        path: /impl.+
        pathType: ImplementationSpecific

 

예상할 수 있듯이, exact 는 경로가 정확히 일치해야 하고, prefix 는 가장 많이 일치하는 접두사의 경로를 라우팅하고, ImplementationSpecific 은 cilium 구현체의 경로에 기반하여 동작합니다.

 

 

Kubernetes Gateway API Support

Gateway API 란?
Gateway API 는 Kubernetes 에서 서비스 노출을 위한 차세대 표준으로, Ingress API 보다 더 확장 가능한 인터페이스를 제공한다.

 

Cilium Gateway API Support 는 Kubernetes Gateway API 에 대한 포괄적인 지원을 제공합니다.

Gateway API 에 대한 내용은 https://kubernetes.io/docs/concepts/services-networking/gateway/ 내용을 참고해주세요.

 

설치

Kubernetes 1.23 이상에서 Gateway API 관련 CRD 를 설치합니다.

 

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_gateways.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.2.0/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml

 

⚠️ 주의 : cilium ingress controller 와 cilium gateway api 는 같이 사용할 수 없습니다.

ingressController.enabled 설정은 비활성화하고, gatewayAPI.enabled 설정은 활성화합니다.

 

helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set ingressController.enabled=false --set gatewayAPI.enabled=true

kubectl -n kube-system rollout restart deployment/cilium-operator
kubectl -n kube-system rollout restart ds/cilium

 

내부적으로 cilium operator 가 gateway api 리소스들을 검증하고, Cilium agent 가 Cilium Envoy Configuration 리소스를 처리하게 됩니다. 

 

실습

cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
spec:
  gatewayClassName: cilium
  listeners:
  - protocol: HTTP
    port: 80
    name: web-gw
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-app-1
spec:
  parentRefs:
  - name: my-gateway
    namespace: default
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /details
    backendRefs:
    - name: details
      port: 9080
  - matches:
    - headers:
      - type: Exact
        name: magic
        value: foo
      queryParams:
      - type: Exact
        name: great
        value: example
      path:
        type: PathPrefix
        value: /
      method: GET
    backendRefs:
    - name: productpage
      port: 9080
EOF

 

같은 네임스페이스로 들어오는 80 트래픽에 대해서, /details URL 은 details 서비스 9080 포트로, /?great=example&magic=foo URL 은 productpage 서비스 9080 포트로 가는 Gateway API 를 설정합니다.

 

 

조건에 부합하도록 페이로드를 작성해보니 정상 통신되고 있습니다. https 도 테스트해보겠습니다.

 

1. 로컬 CA(자체 루트 인증기관) 생성

내 로컬에서 만든 인증서를 시스템이 신뢰하도록 설정해주고, 로컬 CA 를 생성한 다음 시스템에 등록하겠습니다.

 

## mkcert 설치
apt install -y mkcert

## *.cilium.rocks 도메인에 대한 CA 생성
mkcert '*.cilium.rocks'

## 로컬 인증서 신뢰
mkcert -install

## CA 저장 위치 확인
# CA 파일은 $(mkcert -CAROOT)가 가리키는 사용자 데이터 디렉터리에 저장되고 (예: rootCA.pem, rootCA-key.pem) "mkcert -CAROOT" 명령어로 확인합니다.
mkcert -CAROOT

 

2. 생성한 인증서 기반으로 Secret 객체 생성

 

 

cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: tls-gateway
spec:
  gatewayClassName: cilium
  listeners:
  - name: https-1
    protocol: HTTPS
    port: 443
    hostname: "bookinfo.cilium.rocks"
    tls:
      certificateRefs:
      - kind: Secret
        name: demo-cert
  - name: https-2
    protocol: HTTPS
    port: 443
    hostname: "webpod.cilium.rocks"
    tls:
      certificateRefs:
      - kind: Secret
        name: demo-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: https-app-route-1
spec:
  parentRefs:
  - name: tls-gateway
  hostnames:
  - "bookinfo.cilium.rocks"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /details
    backendRefs:
    - name: details
      port: 9080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: https-app-route-2
spec:
  parentRefs:
  - name: tls-gateway
  hostnames:
  - "webpod.cilium.rocks"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: webpod
      port: 80
EOF

 

위와 같이 tls-gateway 를 생성해주고 테스트 해보겠습니다.

 

 

정상 통신됩니다! 이번엔 TLS 를 살펴보겠습니다. TLS 는 SSL Termination (Offloading) 과 SSL Passthrough 모드가 존재합니다.

SSL Termination 은 외부 SSL 트래픽을 복호화하여 내부에 평문 트래픽을 제공하고, SSL Passthrough 모드는 그대로 SSL 트래픽을 제공합니다.

 

 

SSL Termination (Offloading)

 

Client → Gateway: HTTPS
Gateway → Pod: HTTP

 

SSL Passthrough

 

Client → Gateway: HTTPS

Gateway → Pod: HTTPS

 

먼저 Nginx ConfigMap 을 생성하고, 그 ConfigMap 으로 nginx 를 deploy 합니다.

 

cat <<'EOF' > nginx.conf
events {
}

http {
  log_format main '$remote_addr - $remote_user [$time_local]  $status '
  '"$request" $body_bytes_sent "$http_referer" '
  '"$http_user_agent" "$http_x_forwarded_for"';
  access_log /var/log/nginx/access.log main;
  error_log  /var/log/nginx/error.log;

  server {
    listen 443 ssl;

    root /usr/share/nginx/html;
    index index.html;

    server_name nginx.cilium.rocks;
    ssl_certificate /etc/nginx-server-certs/tls.crt;
    ssl_certificate_key /etc/nginx-server-certs/tls.key;
  }
}
EOF

kubectl create configmap nginx-configmap --from-file=nginx.conf=./nginx.conf

 

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
    - port: 443
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - name: my-nginx
          image: nginx
          ports:
            - containerPort: 443
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx
              readOnly: true
            - name: nginx-server-certs
              mountPath: /etc/nginx-server-certs
              readOnly: true
      volumes:
        - name: nginx-config
          configMap:
            name: nginx-configmap
        - name: nginx-server-certs
          secret:
            secretName: demo-cert
EOF

 

TLS Passthrough 모드로 Gateway 를 생성합니다.

 

cat << EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: cilium-tls-gateway
spec:
  gatewayClassName: cilium
  listeners:
    - name: https
      hostname: "nginx.cilium.rocks"
      port: 443
      protocol: TLS
      tls:
        mode: Passthrough
      allowedRoutes:
        namespaces:
          from: All
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  name: nginx
spec:
  parentRefs:
    - name: cilium-tls-gateway
  hostnames:
    - "nginx.cilium.rocks"
  rules:
    - backendRefs:
        - name: my-nginx
          port: 443
EOF

 

 

통신 잘 되네요~!

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] Security  (0) 2025.09.07
[cilium] k8s / cilium 성능 테스트  (0) 2025.08.30
[cilium] cluster mesh  (2) 2025.08.15
[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
반응형

cluster mesh 설치

cluster mesh 를 통해 여러 개의 독립적인 쿠버네티스 클러스터를 하나의 논리적인 네트워크로 연결하여 통합 관리할 수 있습니다.

실습으로 간단히 알아볼 예정이므로 kind 라는 로컬 쿠버네티스 클러스터를 구성할 수 있는 도구를 활용할 예정입니다.

 

 

kind

kind is a tool for running local Kubernetes clusters using Docker container “nodes”. kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI. If you have go 1.17+ and docker, podman or nerdctl installed go

kind.sigs.k8s.io

 

https://kind.sigs.k8s.io/docs/user/quick-start/#installation 페이지를 참고하여, 각 운영체제에 맞게 kind 를 설치해줍니다.

 

# west 클러스터 설정
kind create cluster --name west --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # sample apps
    hostPort: 30000
  - containerPort: 30001 # hubble ui
    hostPort: 30001
- role: worker
  extraPortMappings:
  - containerPort: 30002 # sample apps
    hostPort: 30002
networking:
  podSubnet: "10.0.0.0/16"
  serviceSubnet: "10.2.0.0/16"
  disableDefaultCNI: true
  kubeProxyMode: none
EOF

# east 클러스터 설정
kind create cluster --name east --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 31000 # sample apps
    hostPort: 31000
  - containerPort: 31001 # hubble ui
    hostPort: 31001
- role: worker
  extraPortMappings:
  - containerPort: 31002 # sample apps
    hostPort: 31002
networking:
  podSubnet: "10.1.0.0/16"
  serviceSubnet: "10.3.0.0/16"
  disableDefaultCNI: true
  kubeProxyMode: none
EOF

 

Cluster 객체를 통해 control-plane 노드와 worker 노드를 생성합니다. 아래 클러스터 주소 지정 요구사항을 참고하여 노드를 생성 시, podCIDR 대역과 serviceSubnet 이 겹치지 않도록 설정해야 합니다. 

 

클러스터 주소 지정 요구 사항
- 모든 클러스터는 동일한 데이터 경로 모드를 설정해야 합니다. (캡슐화 또는 네이티브 라우팅 모드)
- 모든 클러스터의 PodCIDR 범위는 충돌하지 않고 고유한 IP 주소여야 합니다.
- 모든 클러스터의 노드는 각 노드에 구성된 InternalIP를 사용하여 서로 IP 연결을 유지해야 합니다.
- 클러스터 간 네트워크는 클러스터 간 통신을 허용해야 합니다.

 

# west 클러스터 설정
cilium install --version 1.17.6 --set ipam.mode=kubernetes \
--set kubeProxyReplacement=true --set bpf.masquerade=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set operator.replicas=1 --set debug.enabled=true \
--set routingMode=native --set autoDirectNodeRoutes=true --set ipv4NativeRoutingCIDR=10.0.0.0/16 \
--set ipMasqAgent.enabled=true --set ipMasqAgent.config.nonMasqueradeCIDRs='{10.1.0.0/16}' \
--set cluster.name=west --set cluster.id=1 \
--context kind-west

# east 클러스터 설정
cilium install --version 1.17.6 --set ipam.mode=kubernetes \
--set kubeProxyReplacement=true --set bpf.masquerade=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set operator.replicas=1 --set debug.enabled=true \
--set routingMode=native --set autoDirectNodeRoutes=true --set ipv4NativeRoutingCIDR=10.1.0.0/16 \
--set ipMasqAgent.enabled=true --set ipMasqAgent.config.nonMasqueradeCIDRs='{10.0.0.0/16}' \
--set cluster.name=east --set cluster.id=2 \
--context kind-east

 

CNI 네트워크 플러그인 cilium 을 설정해줍니다.

cluster mesh 로 각각의 클러스터들을 묶기 위해서 cluster.name 옵션곽 cluster.id 값을 유니크하게 지정해주어야 합니다.

네트워크 설정 시, 보통은 podCIDR 대역을 노드의 라우팅 테이블에 넣어주어야 하는데 native routing 모드이고 cluster mesh 를 구성할 때 자동으로 라우팅을 주입해줍니다. 노드 OS 라우팅 테이블에 라우팅이 되어 있지 않은데도 통신이 정상적으로 됩니다.

 

하지만 하나 체크할 것이 있습니다. cilium 에서 내부 통신할 때, 인증서 기반으로 통신을 하는데 지금 개별 설치를 진행했기 때문에 한 쪽에 있는 인증서 정보로 맞춰주는 작업이 필요합니다.

 

# east 에 있는 ca 정보를 삭제하고,
keast delete secret -n kube-system cilium-ca

# west 에 있는 ca 정보를 복사해서 east ca 에 복사합니다.
kubectl --context kind-west get secret -n kube-system cilium-ca -o yaml | \
kubectl --context kind-east create -f -

 

cluster mesh 구성

일단 cluster mesh 로 서로 간의 정보를 주고 받는 컨트롤 플레인의 역할을 설정해주어야 합니다. 여기서는 간단한 실습 환경을 구성하기 위해 서비스 타입을 NodePort 로 진행합니다.

 

cilium clustermesh enable --service-type NodePort --enable-kvstoremesh=false --context kind-west
cilium clustermesh enable --service-type NodePort --enable-kvstoremesh=false --context kind-east

 

 

NodePort 서비스 타입으로 생성하면 clustermesh 와 관련된 서비스와 파드 정보가 생성된 것을 확인할 수 있습니다.

 

cilium clustermesh connect --context kind-west --destination-context kind-east

 

이후 해당 명령어로 kind-west 와 kind-east 컨텍스트 정보를 연결하면 cluster mesh 구성이 완료됩니다.

 

 

 

OS 레벨 단에도 라우팅 정보가 자동으로 잘 들어간 것을 볼 수 있습니다.

 

cluster mesh 통신 확인

pod 통신 확인

cat << EOF | kubectl apply --context kind-west -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

cat << EOF | kubectl apply --context kind-east -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

west 와 east 사이에 pod 통신을 확인하기 위해서 예제 pod 를 생성합니다.

 

 

kind-west 에서 kind-east 의 curl-pod 로 ping 요청을 해보니 통신도 정상적으로 수행됩니다!

(NAT 없이 요청이 잘 되는 것을 볼 수 있습니다.)

 

Service 통신 확인

# west
cat << EOF | kubectl apply --context kind-west -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
  annotations:
    service.cilium.io/global: "true"
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF

# east
cat << EOF | kubectl apply --context kind-east -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
  annotations:
    service.cilium.io/global: "true"
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF

 

west 와 east 사이에 service 통신을 확인하기 위해서 예제 pod 및 service 를 생성합니다.

service 생성 시, metadata.annotations 에 service.cilium.io/global: "true" 옵션을 활성화해야 service mesh 로 동작하게 됩니다.

 

 

west 나 east 의 어떠한 서비스 IP 로 호출하더라도 모든 west 와 east 의 pod 엔드포인트 IP 가 호출될 수 있음을 확인할 수 있습니다.

 

Service Session Affinity 설정

west 와 east 두 지역에 모두 통신이 가능함은 확인하였지만, 한 지역(west, east) 으로 트래픽을 우선하게 보내도록 할 수도 있습니다.

 

# Session Affinity Local 설정
kwest annotate service webpod service.cilium.io/affinity=local --overwrite
kwest describe svc webpod | grep Annotations -A3

keast annotate service webpod service.cilium.io/affinity=local --overwrite
keast describe svc webpod | grep Annotations -A3

 

 

affinity 설정을 보면 자기 자신의 pod endpoint ip 들이 preferred 설정된 것을 볼 수 있습니다. 자기 자신의 pod 대역들에게 우선으로 트래픽을 보낸다는 뜻입니다.

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] k8s / cilium 성능 테스트  (0) 2025.08.30
[cilium] Ingress / Gateway API  (0) 2025.08.23
[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
[cilium] Overlay Network  (1) 2025.08.09
반응형

podCIDR 대역 BGP 설정

 

지난 시간에 이어, BGP 로 통신하여 네트워크 대역이 다른 문제를 해결해보려고 합니다.

 

 

다른 컴퓨터와 통신하기 위해서는 자신의 IP 를 광고해야 한다고 했는데 네트워크 대역이 다르므로 bgp 를 통해 광고해야 합니다.

k8s 클러스터 내부에서도 bgp 설정이 필요하고, k8s 네트워크에 조인되지 않은 라우터에도 bgp 설정이 필요합니다.

 

먼저 라우터 노드에 frrr 이라는 bgp 소프트웨어를 설치하고, k8s 대역에 있는 노드들을 neighbor 로 등록합니다.

 

cat << EOF >> /etc/frr/frr.conf
  neighbor CILIUM peer-group
  neighbor CILIUM remote-as external
  neighbor 192.168.10.100 peer-group CILIUM
  neighbor 192.168.10.101 peer-group CILIUM
  neighbor 192.168.20.100 peer-group CILIUM 
EOF

systemctl daemon-reexec && systemctl restart frr

 

위와 같이, /etc/frr/frr.conf 파일을 직접 수정해도 되고 아래와 같이 라우터 노드에 접근해서 설정을 해줘도 됩니다.

 

# 1. 터미널 진입
vtysh

# 2. 구성 설정 확인
conf

#3. 라우터 설정
router bgp 65000
neighbor CILIUM peer-group
neighbor CILIUM remote-as external
neighbor 192.168.10.100 peer-group CILIUM
neighbor 192.168.10.101 peer-group CILIUM
neighbor 192.168.20.100 peer-group CILIUM 
end

#4. 메모리 저장
write memory

 

k8s 의 control plane 노드에서도 cilium bgp 설정을 통해 router 노드를 neighbor 로 등록합니다.

먼저, bgp 동작할 노드들을 선정하기 위해서 노드에 label 을 붙여줍니다.

 

kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
kubectl get node -l enable-bgp=true

 

이후 CiliumBGPClusterConfig, CiliumBGPPeerConfig, CiliumBGPAdvertisement 설정을 통해 neighbor 를 등록합니다.

 

cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
  name: bgp-advertisements
  labels:
    advertise: bgp
spec:
  advertisements:
    - advertisementType: "PodCIDR"
---
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
  name: cilium-peer
spec:
  timers:
    holdTimeSeconds: 9
    keepAliveTimeSeconds: 3
  ebgpMultihop: 2
  gracefulRestart:
    enabled: true
    restartTimeSeconds: 15
  families:
    - afi: ipv4
      safi: unicast
      advertisements:
        matchLabels:
          advertise: "bgp"
---
apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
  name: cilium-bgp
spec:
  nodeSelector:
    matchLabels:
      "enable-bgp": "true"
  bgpInstances:
  - name: "instance-65001"
    localASN: 65001
    peers:
    - name: "tor-switch"
      peerASN: 65000
      peerAddress: 192.168.10.200  # router ip address
      peerConfigRef:
        name: "cilium-peer"
EOF

 

CiliumBGPClusterConfig

  • 역할: 클러스터 내 bgp 인스턴스들과 neighboor 노드들 간의 peer 설정
  • 특징: nodeSelector 를 통해 특정 노드들을 선택하여 광고 가능 (일관된 BGP 설정 관리)

CiliumBGPPeerConfig

  • 역할: CiliumBGPClusterConfig 에 등록된 클러스터 내 bgp 인스턴스의 BGP 피어링 설정
  • 특징: neighbor 설정과 peering 상세 설정을 분리하여 여러 피어에서 동일한 설정 공유 가능

CiliumBGPAdvertisement

  • 역할: BGP 라우팅 테이블에 주입할 광고 유형 정의 (Pod CIDR, Service IP 등 다양한 타입 지원)

65000 AS 인 192.168.1.200 라우터와 neighboor 설정하고 ipv4의 podCIDR 대역을 홍보하는 설정입니다.

모든 설정이 완료되면, k8s control plane 노드에서 bgp peers 설정을 확인할 수 있습니다.

 

 

라우터 노드에서도 k8s 의 podCIDR 대역이 라우팅 된 것을 확인할 수 있습니다.

 

 

하지만 아직도 다른 네트워크 간 통신이 되지 않고 있습니다. BGP 프로토콜로 인해 다른 neighbor 에 있는 podCIDR 대역을 받아왔지만,

커널 라우팅 테이블에는 자동으로 올라가지 않았습니다. 여전히 라우팅 테이블에 podCIDR 대역이 보이지 않네요.

 

 

Cilium의 BGP는 GoBGP 기반으로 구성되어 있는데 기본적으로 disable-telemetry, disable-fib 상태로 빌드가 되기 때문에, 수신한 경로를 Linux 커널(FIB) 에 바로 주입하지 않는 것으로 추정됩니다. 여러 개의 podCIDR 대역들을 네트워크 장비에 전달하면 그 장비가 직접 라우팅을 진행하면 되기 때문에 Cilium 입장에서는 받을 필요가 없을 수도 있을 것 같습니다.

 

실무 환경에서는 default gateway 가 존재할 것이기 때문에 통신은 무난히 진행될 것으로 생각됩니다. 다만, 실습 환경에서는 k8s 대역을 eth1 인터페이스로, 인터넷을 eth0 인터페이스로 사용하고 있기 때문에 podCIDR 대역을 eth1 통해서 라우팅 될 수 있도록 조정이 필요합니다.

 

# k8s 파드 사용 대역 통신 전체는 eth1을 통해서 라우팅 설정
ip route add 172.20.0.0/16 via 192.168.10.200
sshpass -p 'vagrant' ssh vagrant@k8s-w1 sudo ip route add 172.20.0.0/16 via 192.168.10.200
sshpass -p 'vagrant' ssh vagrant@k8s-w0 sudo ip route add 172.20.0.0/16 via 192.168.20.200

 

 

이제 정상적으로 통신이 되고 있습니다.

 

Service IP BGP 설정

 

지금까지 알아본 BGP 설정은 podCIDR 대역을 홍보한 설정이지만, 저번 시간에 알아본 Service IP (External IP) 는 어떻게 설정해야 할까요?

 

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-pool"
spec:
  allowFirstLastIPs: "No"
  blocks:
  - cidr: "172.16.1.0/24"
EOF

 

로 IPAM 을 설정하고, 서비스의 ip 를 'LoadBalancer' 타입으로 변경하면 IPAM 에서 IP 를 할당 받습니다.

 

cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
  name: bgp-advertisements-lb-exip-webpod
  labels:
    advertise: bgp
spec:
  advertisements:
    - advertisementType: "Service"
      service:
        addresses:
          - LoadBalancerIP
      selector:             
        matchExpressions:
          - { key: app, operator: In, values: [ webpod ] }
EOF

 

위에서 BGP peering 설정은 완료했으니 "metadata.labels.advertise: bgp" 라벨만 고정으로 해서 Service 유형의 CiliumBGPAdvertisement 를 통해 서비스 IP 를 광고해주면 됩니다.

 

 

정상적으로 router 노드에 Service IP 가 라우팅 되는 것을 확인할 수 있습니다. 로드밸런싱도 정상적으로 수행이 될까요?

 

 

router 노드에서 서비스로 통신 시도 시 트래픽이 골고루 분산되는 것처럼 보입니다. 현재 3개의 replica 구성을 2개의 replica 구성으로 변경해보았습니다.

 

 

2개의 replica 로 줄어들면서 k8s-ctr 노드와 k8s-w0 노드에만 webpod 가 세팅이 되어 있지만, bgp 라우팅에는 3개의 노드가 아직 구성되어 있습니다. 이유는 Service 의 externalTrafficPolicy 속성이 cluster(default) 이기 때문입니다. cluster 타입을 local 로 변경하면 pod 를 가지고 있는 노드만 광고하게 됩니다. (아래 정보에서 2번 방식에서 1번 방식으로 변경된 것입니다.)

 

더보기

(1) BGP(ECMP) + Service(LB EX-IP, ExternalTrafficPolicy:Local) + SNAT + Random

1. Router 에서 BGP ECMP MultiPath 로 인해 파드가 있는 노드로만 전달

2. 해당 노드의 파드가 요청을 처리하고 Router 로 바로 응답 리턴

 

(2) BGP(ECMP) + Service(LB EX-IP, ExternalTrafficPolicy:Cluster) + SNAT → 비권장 방식

 

1. Router 에서 BGP ECMP MultiPath 로 인해 Cilium BGP Peer 중 하나의 노드로 전달
2. 해당 트래픽의 원래 목적지인 k8s-ctr 의 파드로 요청을 전달
3. 해당 노드의 파드가 요청을 처리하고 응답 리턴을 위해서, NAT 를 수행했던 노드(k8s-w1) 로 다시 전달
4. NAT 를 수행했던 연결 정보를 확인해서, Reverse NAT 를 수행해서 최종 응답을 리턴

 

(3) BGP(ECMP) + Service(LB EX-IP, ExternalTrafficPolicy:Cluster) + DSR + Maglev

 

1. Router 에서 BGP ECMP MultiPath 로 인해 Cilium BGP Peer 중 하나의 노드로 전달
2. 해당 트래픽의 원래 목적지인 k8s-ctr 의 파드로 요청을 전달

이 때, Router 로 바로 리턴하기 위해서(DSR) 최초 접속했던 클라이언트의 정보를 GENEVE 헤더에 감싸 전달

(클라이언트의 세션에 대한 유지를 위해서 Maglev 알고리즘을 사용)

3. 해당 노드의 파드가 요청을 처리하고 GEVEVE 헤더 정보를 활용하여 Reverse NAT 을 수행해서 응답을 바로 리턴

 

kubectl patch service webpod -p '{"spec":{"externalTrafficPolicy":"Local"}}'

 

 

pod 가 있는 노드만 라우팅 설정된 것을 볼 수 있습니다. 하지만, 아직도 부하 분산이 되지 않고 있습니다. 한 쪽에 있는 pod 에만 트래픽이 몰리고 있는데요. 이는 리눅스에서 네트워크 분산을 책임지는 ECMP Hash 정책이 기본적으로 L3(목적지 IP 기반) 해시를 사용하기 때문입니다.

 

 

정교한 부하분산을 원한다면 L4 해시 (IP + 포트) 기반으로 설정해야 합니다.

 

sudo sysctl -w net.ipv4.fib_multipath_hash_policy=1

 

 

부하 분산이 정상적으로 되는 것을 확인할 수 있습니다. replica 를 다시 3개로 증가시켜 부하 분산이 되는지 마지막으로 체크해보겠습니다.

 

kubectl scale deployment webpod --replicas 3

 

 

라우팅도 바로 원복되고, 부하 분산도 잘 되고 있습니다.

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] Ingress / Gateway API  (0) 2025.08.23
[cilium] cluster mesh  (2) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
[cilium] Overlay Network  (1) 2025.08.09
[cilium] Routing & Masquerading  (1) 2025.07.31
반응형

LoadBalancer 서비스 타입

Kubernetes 의 NodePort 서비스는 각 서비스마다 고유한 IP 를 가질 수 있지만, 클라이언트가 어느 호스트로 트래픽을 보낼지 결정해야 하는 단점이 있습니다. 또한 노드가 다운되면 IP + Port 조합 사용이 불가합니다. LoadBalncer 서비스 타입은 이러한 단점을 해결하고자 외부에서 IP 를 할당해주고, 어떤 IP 로 트래픽을 보내야 할지 알려줍니다. 클라이언트는 LoadBalancer 서비스 타입의 IP 만 알고 있으면 됩니다. 아래와 같이 Kubernetes에서 LoadBalancer 타입 서비스를 만들면,

apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: my-app

 

클라우드에서는 AWS, GCP, Azure 같은 클라우드 제공업체가 자동으로 로드밸런서를 만들어주고 IP를 할당해주지만, 온프레미스 환경에서는 아무도 IP 를 할당해주지 않아서 아래처럼 계속 <pending> 상태로 남아있게 됩니다.

NAME     TYPE           EXTERNAL-IP   PORT(S)
my-app   LoadBalancer   <pending>     80:30080/TCP

 

Cilium 에서는 이러한 역할을 LoadBalancer IPAM 이 담당합니다. LoadBalancer 서비스 유형에서 IP 를 할당하고, 로드밸런싱 역할을 수행합니다. 하지만 IP 를 각 노드에 할당을 해준다고 해서 통신이 가능할까요? 자신의 주소는 받았지만, 경로는 설정되지 않아서 도달할 수 없습니다. 누군가(라우터 or 스위치)가 경로를 안내해주어야 하지만, 자신이 어디있다고 말하지 않으면 네트워크 관리자가 매번 라우터 or 스위치에 수동적으로 경로 테이블을 변경해야 할 것입니다. 따라서 어떻게 자신의 주소를 홍보할 것인지 방법이 필요합니다.

 

같은 네트워크 대역이라면 ARP 프로토콜을 통해 자신의 주소를 알려줄 수 있고, 다른 네트워크 대역이라면 BGP 프로토콜을 통해 자신의 주소를 홍보할 수 있습니다. cilium 에서 어떻게 설정할 수 있는지 아래에서 살펴보겠습니다.

 

Cilium LoadBalncer IPAM (서비스의 IP 분배 방법)

Cilium 에서 LoadBalancer IPAM 은 IP 를 Pool 형태로 관리하고, 필요 시 Pool 에서 할당하고 해제 시 Pool 에 반납합니다.

apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  blocks:
  - start: "192.168.10.211"
    stop: "192.168.10.215"

 

blocks 문법 형태로 start 와 stop 사이의 연속된 범위를 지정할 수 있고,

apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  cidrs:
  - "192.168.10.0/24"

 

cidrs 문법 형태로 서브넷 마스크를 활용하여 CIDR 대역을 지정할 수도 있습니다. 혼용하여 사용도 가능합니다.

보통 blocks 문법은 불연속적인 IP 범위를 각각 지정하여 정확한 IP 를 분배할 때 사용하며, CIDR 문법은 CIDR 대역으로 유연하게 관리할 때 사용합니다.

 

 

위와 같은 환경에서 아래와 같이 ip pool 을 생성 후, LoadBalancer 타입의 서비스를 생성해보면,

ippool 중 하나의 ip 가 할당되는 것을 볼 수 있습니다.

 

내부 클러스터끼리도 정상 통신하고 있습니다.

 

하지만 k8s 클러스터 외부인 라우터에서 접속하면 아래와 같이 통신이 되지 않는 것을 볼 수 있습니다.

 

앞서 살펴보았듯이, 다른 네트워크 대역과 통신하려면 서비스의 IP 를 홍보해야 합니다.

 

Cilium 의 IP 홍보방법

cilium 의 LoadBalancer IPAM 에서 Cilium BGP Control Plane 사용하여 BGP 프로토콜을 통해 IP 를 홍보하고, L2 Announcements/L2 Aware LB(베타)를 통해 로컬에 자신의 주소를 홍보합니다.

Cilium L2 Announcement

로컬 영역 네트워크(LAN) 에서 External IP 나 LoadBalancer 유형의 IP 요청이 왔을 때 자신의 ARP 주소를 응답 쿼리로 보냄으로써 통신이 수행됩니다. 가장 빠르게 ARP 요청을 보낸 노드가 리더 노드가 되어 라우팅 역할을 수행하게 됩니다. 나머지 노드는 백업 상태로 유지되다가 리더 노드가 죽으면 다시 빠르게 ARP 요청을 보낸 노드를 리더로 선출합니다. 이처럼 항상 리더 노드를 거쳐 트래픽이 이동해야 된다는 단점이 있다는 것을 알 수 있습니다.

helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values \
 --set l2announcements.enabled=true
   
kubectl rollout restart -n kube-system ds/cilium

 

l2announcements.enabled 옵션을 활성화하고 rollout 하여 announce 기능을 활성화합니다.

Cilium L2 Announcement Policy 정책으로 특정 서비스와 특정 노드에 필터링을 걸어 조금 더 세밀하게 제어할 수 있습니다.

apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  serviceSelector:
    matchLabels:
      app: webpod
  nodeSelector:
    matchExpressions:
      - key: kubernetes.io/hostname
        operator: NotIn
        values:
          - k8s-w0
  interfaces:
  - ^eth[1-9]+
  externalIPs: true
  loadBalancerIPs: true

 

webpod 서비스를 노출하는 externalIP 와 loadbalncerIP 유형들에 대해 k8s-w0 을 제외한 노드가 ARP 응답을 보낼 수 있도록 정책을 구성하였습니다. 앞서 본 테스트 환경에서 k8s-w0 노드는 k8s 클러스터 내에 있지 않고 같은 네트워크 영역에 있지 않으므로 ARP 를 광고할 수 있는 노드에서 배제해야 합니다.

 

 

정책을 적용하고 나서 현재 상태를 확인해보니 k8s-w1 노드가 리더로 선출된 것을 확인할 수 있습니다.

 

 

cilium-dbg shell -- db/show l2-announce  명령어를 통해서도 현재 서비스 IP 가 어떤 노드에게 할당되어 있는지 확인 가능합니다.

 

 

아까와 다르게 router 노드와 k8s-cluster 간 통신이 정상적으로 수행되는 것을 볼 수 있습니다.

 

 

router 노드의 ARP 테이블도 확인해보면 서비스(192.168.10.211)의 ARP 가 현재 리더 노드(192.168.10.101)의 ARP 와 같습니다.

리더 노드가 모든 트래픽을 받아 원래 도착지 주소로 보냄으로써 통신한게 됩니다.

 

Cilium LoadBalncer IPAM 추가 기능

cilium <-> router 간 노드가 정상적으로 통신된 상태에서 cilium LoadBalancer IPAM 의 추가 기능을 살펴보겠습니다.

추가 기능을 살펴보기 앞서 netshoot 을 개량하여 만든 웹 서비스를 하나 더 띄워 보겠습니다.

 

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-web
  labels:
    app: netshoot-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: netshoot-web
  template:
    metadata:
      labels:
        app: netshoot-web
    spec:
      terminationGracePeriodSeconds: 0
      containers:
        - name: netshoot
          image: nicolaka/netshoot
          ports:
            - containerPort: 8080
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          command: ["sh", "-c"]
          args:
            - |
              while true; do 
                { echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nOK from \$POD_NAME"; } | nc -l -p 8080 -q 1;
              done
EOF

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: netshoot-web
  labels:
    app: netshoot-web
spec:
  type: LoadBalancer
  selector:
    app: netshoot-web
  ports:
    - name: http
      port: 80      
      targetPort: 8080
EOF

 

서비스 생성 이후, CiliumL2AnnouncementPolicy 정책을 하나 더 생성하면

 

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"  # not v2
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy2
spec:
  serviceSelector:
    matchLabels:
      app: netshoot-web
  nodeSelector:
    matchExpressions:
      - key: kubernetes.io/hostname
        operator: NotIn
        values:
          - k8s-w0
  interfaces:
  - ^eth[1-9]+
  externalIPs: true
  loadBalancerIPs: true
EOF

 

 

Requesting IPs

Service 에 External IP 를 직접 설정할 수 있는 기능으로 CiliumLoadBalancerIPPool 으로 선언한 IP Pool 에 해당하는 범위 내에 특정 IP 들을 직접 선택할 수 있습니다.

 

## metadata.annotations 아래에 추가
  annotations:
    "lbipam.cilium.io/ips": "192.168.10.215"

 

Service 객체의 "lbipam.cilium.io/ips" 어노테이션으로 활성화할 수 있습니다.

 

 

현재는 192.168.10.212 IP 를 사용하고 있지만,

 

 

annotation 적용 후 192.168.10.215 IP 로 변경한 것을 볼 수 있습니다.

 

Sharing Keys

Service 간 External IP 를 같이 사용하고자 할 때 사용합니다. Service 객체의 "lbipam.cilium.io/sharing-key" 어노테이션으로 활성화할 수 있습니다.

# metadata.annotations 아래 아래 추가
  annotations:
  	# "lbipam.cilium.io/ips": "192.168.10.215" 같은 IP 로 세팅
    "lbipam.cilium.io/sharing-key": "1234"

 

포트만 다른 동일한 서비스를 하나 더 만들어 보고 같은 IP 를 공유할 수 있는지 체크해보겠습니다.

 

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: netshoot-web2
  labels:
    app: netshoot-web
spec:
  type: LoadBalancer
  selector:
    app: netshoot-web
  ports:
    - name: http
      port: 8080      
      targetPort: 8080
EOF

 

위에서 생성한 netshoot-web 서비스에도 같은 sharing-key 를 넣어주어야 합니다.

 

 

netshoot-web 서비스와 netshoot-web2 서비스의 IP 가 같아진 것을 볼 수 있습니다.

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] cluster mesh  (2) 2025.08.15
[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] Overlay Network  (1) 2025.08.09
[cilium] Routing & Masquerading  (1) 2025.07.31
[cilium] IPAM  (3) 2025.07.31
반응형

< 오타 > k8s-w1 eth1 인터페이스 192.168.10.101 입니다.

 

  • 실습 환경
    • 목적: 다른 네트워크에 있는 노드 간 쿠버네티스 클러스터 통신을 확인하기 위함
    • router 노드: k8s 에 join 되지 않은 서버로 192.168.10.0/24 ↔ 192.168.20.0/24 대역 라우팅 역할 수행
    • k8s-w0 노드: k8s-ctr, k8s-w1 노드와 다른 네트워크 대역에 배치
    • k8s-w1 노드: k8s-ctr 과 같은 네트워크 대역에 배치
더보기

[ k8s-ctr -> k8s-w0 라우팅 ]

 

k8s-w0 대역 요청 시, router의 eth1 인터페이스로 이동하여 통신하게 됩니다.

router node 의 eth1, eht2 인터페이스가 게이트웨이 역할을 하여 노드 간 통신은 할 수 있는 상태입니다.

 

Overlay Network 가 필요한 이유 

쿠버네티스 환경에서 위와 같이 클러스터 대역 중 일부 노드가 다른 네트워크 대역을 사용한다면, 상호 간 통신이 불가합니다. 위 실습 환경에서는 k8s-w0 노드와 k8s-ctr, k8s-w1 노드 간 통신이 불가한 거죠.

 

cilium 을 설치할 때 autoDirectNodeRoutes=true 옵션을 사용하는데 자동으로 라우팅을 해주지 않는 걸까요?

 

helm install cilium cilium/cilium --version $2 --namespace kube-system \
--set k8sServiceHost=192.168.10.100 --set k8sServicePort=6443 \
--set ipam.mode="cluster-pool" --set ipam.operator.clusterPoolIPv4PodCIDRList={"172.20.0.0/16"} --set ipv4NativeRoutingCIDR=172.20.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=true \

... 생략 ...

 

autoDirectNodeRoutes 옵션은 같은 네트워크 대역 노드 간에 pod CIDR 대역을 static routing 방식으로 자동으로 추가해주는 옵션입니다. 다른 네트워크 대역의 경우, 중간에 네트워크 장비가 pod CIDR 대역을 라우팅 안 해주면 의미가 없기 때문에 라우팅 추가를 하지 않습니다.

 

[ k8s-ctr 노드의 라우팅 테이블 ]

 

172.20.0.0/16 pod CIDR 대역 중 172.20.1.0/24 pod 네트워크만 k8s-w1(192.168.10.101) 노드로 라우팅이 되어 있지만, 172.20.2.0/24 pod CIDR 대역 대의 라우팅(k8s-w0 노드)이 보이지 않습니다.

 

[ k8s-w1 노드의 라우팅 테이블 ]

 

172.20.0.0/16 pod CIDR 대역 중 172.20.0.0/24 pod 네트워크만 k8s-ctr(192.168.10.100) 노드로 라우팅이 되어 있지만, 172.20.2.0/24 pod CIDR 대역 대의 라우팅(k8s-w0 노드)이 보이지 않습니다.

 

[ k8s-w0 노드의 라우팅 테이블 ]

 

172.20.2.0/24 pod CIDR 자신의 네트워크 대역은 cilium 이 활성화되어 있지만, k8s-ctr 노드와 k8s-w1 노드로 라우팅되어 있지 않습니다. 실제 패킷 캡처를 통해 네트워크 통신 여부도 살펴보겠습니다.

 

 

다른 네트워크 대역으로 통신 시 router 를 지나가니, router 노드에서 tcpdump 로 패킷을 스니핑합니다. k8s-ctr 서버에 위치한 curl-pod 로 k8s-w0 에 위치한 webpod 로 통신 시, 정상적으로 통신이 되지 않는 현상을 볼 수 있습니다.

아래 캡쳐된 네트워크 패킷들을 보면, eth1 로 들어왔는데 인터넷 전용 인터페이스인 eth0 으로 빠져나갔습니다. (eth0 인터페이스는 외부에서 쿠버네티스에 접근하기 위한 용도로 k8s 클러스터에서 접근할 일이 없습니다!)

 

통신 불가의 문제를 해결하려면 static routing 을 노드가 생성될 때마다 추가할 수 있지만 수동 처리에는 한계가 있습니다.

BGP, Encapsulation (Overlay Network) 방법들을 통해 문제를 해결합니다.

 

Encapsulation (Overlay Network)

설령 pod 대역 대의 IP 주소를 알지 못 하더라도 라우터는 노드의 IP 주소를 알고 라우팅을 할 수 있기 때문에, 노드의 IP 주소로 한 번 감싸 패킷을 보내면 통신이 가능해집니다.

 

 

여타 다른 CNI 플러그인과 같이 VXLAN 모드, GENEVE 모드를 통해 원본 패킷을 감싸 보낼 수 있습니다. 다른 네트워크 플러그인에서는 Overlay Network 라고 부르는데 Cilium 에서는 encapsulation 이라고 부릅니다.

 

Encapsulation 설정

VXLAN 과 GENEVE 를 활성화하려면 CONFIG_VXLAN 과 CONFIG_GENEVE 커널 설정을 활성화 해야 합니다.

아래와 같이 커널 옵션을 살펴보면 'm' 모듈로 컴파일되어 있어 직접 커널에 로드해서 사용합니다. modprobe 지시자로 원하는 모드를 선택하여 Overlay Network 를 활성화합니다.

 

 

나머지 두 노드에도 똑같이 설정을 해줍니다.

 

 

커널 설정 완료 후 helm routingMode=tunnel 옵션과 tunnelProtocol 옵션을 활성화 하여 cilium 을 rollout 해주면 모든 세팅이 완료됩니다.

 

helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values \
  --set routingMode=tunnel --set tunnelProtocol=vxlan \
  --set autoDirectNodeRoutes=false --set installNoConntrackIptablesRules=false

kubectl rollout restart -n kube-system ds/cilium

 

 

cilium 의 속성을 확인해보면 routing 모드가 tunnel 유형으로 바뀌고 tunnel 프로토콜이 vxlan 으로 변경된 것을 확인할 수 있습니다.

완료 후 변화된 점들을 하나씩 알아보겠습니다. 클러스터에 존재하는 서버들에 cilium_vxlan 인터페이스가 새롭게 생성되었습니다.

 

 

라우팅 테이블도 클러스터의 pod CIDR 대역들이 모두 올라온 것을 확인할 수 있습니다.

 

 

k8s-ctr 서버에서는 podCIDR 대역이 172.20.0.98 로 라우팅 되고, k8s-w1 서버에서는 podCIDR 대역이 172.20.1.11 로 라우팅 되고, k8s-w0 서버에서는 podCIDR 대역이 172.20.2.71 로 라우팅 되는데 이 세 IP 가 cilium 에서 라우팅을 위해 만든 IP 입니다.

 

# cilium 파드 이름 지정
export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-ctr -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1  -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD2=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w0  -o jsonpath='{.items[0].metadata.name}')
echo $CILIUMPOD0 $CILIUMPOD1 $CILIUMPOD2

# router 역할 IP 확인
kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router

 

Encapsulation 통신 과정

샘플 애플리케이션을 활용하여 통신 과정을 살펴보겠습니다.

자기 자신이 누구인지 알려주는 3 개의 webpod 와 컨트롤 노드에만 배치할 curl-pod 를 배포하고, router 노드에서 패킷들을 확인해봅니다.

 

# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

 

컨트롤 노드의 curl-pod 에서 다른 네트워크 대역인 k8s-w0 노드로 ping 을 보내고, router 노드에서는 tcpdump -i any udp port 8472 -nn 명령어를 통해 VXLAN 패킷을 잡아보겠습니다.

 

 

 

eth1 인터페이스로 k8s-ctr 노드에서 k8s-w0 노드로 통신하는 패킷 요청이 들어왔고, (overlay 라고 표시되어 있음)

내부 파드 IP 호출 흐름이 한 번 표시된 다음 eth2 인터페이스로 나가는 것을 볼 수 있습니다.

 

termshark 로도 확인해보겠습니다.

 

# 반복 접속
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'

# 신규 터미널 [router] 로 저장하고,
tcpdump -i any udp port 8472 -w /tmp/vxlan.pcap
# termshark 로 열어서 확인
termshark -r /tmp/vxlan.pcap -d udp.port==8472,vxlan

 

termshark 가 overlay network 를 인식하지 못하므로 -d upd.port=8472,vxlan 옵션을 통해 decapsulation 합니다.

 

 

마찬가지로 IP Header 가 중복으로 보이며 VXLAN 헤더가 존재하는 것을 볼 수 있습니다.

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] BGP 설정 방법  (1) 2025.08.15
[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
[cilium] Routing & Masquerading  (1) 2025.07.31
[cilium] IPAM  (3) 2025.07.31
[cilium] Hubble Exporter  (2) 2025.07.23
반응형

Cilium 라우팅 방식

cilium 라우팅 방식을 설명하기 앞서, 쿠버네티스의 파드 IP 통신 규약을 잠시 살펴봅니다.

 

쿠버네티스 네트워크 모델은 아래 4가지 요구사항을 만족해야 합니다.

  1. 파드와 파드 간 통신 시 NAT 없이 통신이 가능해야함
  2. 노드의 에이전트(예. kubelet, 시스템 데몬)는 파드와 통신이 가능해야함
  3. 호스트 네트워크를 사용하는 파드는 NAT 없이 파드와 통신이 가능해야함
  4. 서비스 클러스터 IP 대역과 파드가 사용하는 IP 대역은 중복되지 않아야함

이 중 첫 번째 "NAT 없이 통신" 이라는 표현이 헷갈릴 수 있는데, 파드들은 서로의 IP 만 보고 통신할 수 있다는 의미입니다.
예를 들어, 파드 A(10.244.1.5)에서 파드 B(10.244.2.10)로 패킷을 보낼 때 파드 B가 받는 패킷의 source IP는 10.244.1.5 그대로 유지됩니다. 이렇게 하기 위해서는 CNI 가 IP 를 변환해주어야 한다는 것입니다. 이러한 방식을 지원하기 위해 cilium 라우팅은 캡슐화 방식과 네이티브 라우팅 방식이 존재합니다.

 

1. 캡슐화(Encapsulation) 방식

특징

  • 기본 동작 모드로 VXLAN(8472/UDP) 또는 Geneve(6081/UDP) 사용
  • 모든 노드가 터널 메시를 구성하여 트래픽 캡슐화
  • 기본 네트워크가 PodCIDR 을 인식할 필요 없음

장점

  • 단순성: 네트워크 토폴로지와 무관하게 동작
    (네트워크가 podCIDR 을 인식할 필요 없고 서로 도달만 가능하다면 기본 네트워크 토폴리지는 중요하지 않음)
  • 주소 공간: 더 큰 주소 공간 활용 가능
    (새로운 IP로 캡슐화 하기 때문에 기본 네트워킹 주소 크기에 의존하지 않음)
  • 자동 구성: 새 노드 자동 메시 통합
    (Kubernetes 와 같은 오케스트레이션 시스템과 함께 실행될 때, 할당 관련 접두사를 포함한 클러스터의 모든 노드 목록이 각 에이전트에 자동으로 제공)
  • 신원 컨텍스트: 메타데이터와 보안 관련 Identifier 전송 가능
    (캡슐화 프로토콜은 네트워크 패킷과 함께 메타데이터를 전달 가능)

단점

  • MTU 오버헤드: 패킷당 50바이트 오버헤드로 처리량 감소 (점보 프레임으로 완화 가능)

캡슐화 방식은 Cilium 노드들이 서로 연결될 수 있고, 네트워크와 방화벽 정책에서 캡슐화된 패킷을 허용한다면 라우팅 요구사항이 충족됩니다. 하지만 IPv4 기반 터널링은 완전히 지원하지만, IPv6 기반 터널링은 아직 완전히 지원되지 않습니다. 캡슐화로 인한 성능 오버헤드도 존재합니다. 하지만 대부분의 환경에서 안정적으로 동작하며 복잡한 네트워크 토폴로지에서도 문제없이 사용할 수 있습니다.

 

2. 네이티브 라우팅(Native-Routing) 방식

설정

routing-mode: native
ipv4-native-routing-cidr: x.x.x.x/y

 

 

routing-mode: native 구문은 네이티브 라우팅 모드를 활성화하며, ipv4-native-routing-cidr: x.x.x.x/y 구문은 네이티브 라우팅이 수행되는 CIDR을 설정합니다.

BGP 데몬이 실행 중이지만, 클러스터 네트워크에 기본 서브넷이 있는 경우 트래픽을 항상 BGP 라우터에 라우팅할 필요는 없습니다.

각 노드에 L2 연결을 보장하기 위해 아래와 같이 옵션을 추가할 수 있습니다.

  • direct-routing-skip-unreachable: true (auto-direct-node-routes 옵션과 함께 사용)

네트워크 요구사항

네이티브 라우팅은 네이티브 패킷 포워딩 모드를 활성화하여 Cilium 이 직접 네트워크의 라우팅을 수행합니다. 즉, 클러스터 노드를 연결하는 네트워크는 podCIDR 를 라우팅할 수 있어야 합니다.

즉 노드의 Linux 커널은 Cilium 을 실행하는 모든 노드의 Pod 또는 기타 워크로드 패킷을 전달하는 방법을 알고 있어야 합니다. 이는 두 가지 방법으로 달성할 수 있습니다.

  • 다른 모든 pod 에 전달할 수 있는 라우터에 위임 (Linux 노드는 해당 라우터를 가리키는 기본 경로를 포함)
    => 해당 모델은 주로 클라우드 공급자 네트워크 통합에 사용
  • 각 노드가 다른 모든 파드의 IP 들을 인식 (Linux 커널 라우팅 테이블에 삽입) (auto-direct-node-routes: true)
    모든 노드가 L2 에 존재한다면 옵션을 활성화하여 처리 가능하지만, 그렇지 않은 경우 BGP 데몬을 활용하여 경로 전파 필요

네이티브 라우팅은 캡슐화 오버헤드 없이 최고 성능을 제공하여 클라우드 환경이나 BGP를 지원하는 네트워크에서 주로 사용됩니다.

 

3. AWS ENI 방식

설정

ipam: eni
enable-endpoint-routes: "true"
auto-create-cilium-node-resource: "true"
egress-masquerade-interfaces: eth+

 

AWS ENI 방식을 사용하려면 ipam 을 eni 모드로 변경해야 합니다. (ipam: eni)

enable-endpoint-routes: "true" 옵션은 ENI veth 쌍으로 직접 라우팅 (cilium_host 인터페이스로 라우팅 불필요)

auto-create-cilium-node-resource: "true" 옵션은 모든 필수 ENI 매개변수를 세팅하는 CiliumNode 리소스를 자동 생성

(이 기능을 비활성화하고 사용자가 리소스를 수동으로 제공할 수도 있습니다.)
egress-masquerade-interfaces: eth+ 옵션은 마스커레이딩 대상을 지정할 수 있는 인터페이스 선택자
(enable-ipv4-masquerade: "false" 옵션을 통해 마스커레이딩을 완전히 비활성화할 수 있습니다)

장점

  • 라우팅 불필요
    (AWS VPC 에서 직접 라우팅 가능한 ENI IP 를 할당. VPC 내 포드 트래픽 통신이 간소화되고 SNAT 불필요)
  • 보안 그룹 생성 가능
    (pod의 보안 그룹은 노드별로 구성. 노드 풀을 생성하고 각 pod에 서로 다른 보안 그룹을 할당할 수 있음)

단점

  • IP 제한
    (EC2 인스턴스당 ENI IP 수가 제한되어 있음)
  • IP 할당 지연
    (ENI 및 ENI IP 할당에는 속도 제한이 적용되는 EC2 API 와 상호작용하여 속도 제한됨)

구성

  • ingress: ENI 인터페이스(ethN)에서 트래픽 수신 후 veth 쌍으로 전달
  • egress: 파드에서 기본 경로를 통해 ENI 별 라우팅 테이블 사용

AWS ENI 방식은 AWS 네이티브 네트워킹의 장점을 최대한 활용하면서 파드에 직접 VPC IP를 할당합니다. 하지만 인스턴스 타입에 따른 ENI 제한과 EC2 API 의존성을 고려해야 합니다.

 

4. Google Cloud 방식

설정

gke.enabled: true  # 다음 옵션들 자동 활성화:
  # ipam: kubernetes
  # routing-mode: native  
  # enable-endpoint-routes: true
ipv4-native-routing-cidr: x.x.x.x/y

 

gke.enabled: true 옵션으로 google cloud 방식을 사용할 수 있으며, 아래 옵션들이 자동 활성화된다.

  • ipam: kubernetes: Kubernetes 호스트 범위 IPAM 활성화
  • routing-mode: native: 네이티브 라우팅 모드 활성화
  • enable-endpoint-routes: true: 노드에서 엔드포인트별 라우팅 활성화(로컬 노드 경로를 자동으로 비활성화)

ipv4-native-routing-cidr: x.x.x.x/y 옵션은 네이티브 라우팅이 지원되는 CIDR을 설정합니다.

주요 특징

  • 라우팅 불필요
    (Cilium 이 특정 쿠버네티스 노드에 할당된 PodCIDR을 기반으로 파드에 별칭 IP 를 할당하면 Google Cloud 네트워크에서 라우팅)
  • Masquerading 지원
    (Cluster CIDR 외부 영역으로 트래픽 이동 시 자동으로 라우팅 가능한 IP 주소로 Masquerading 변환)
  • eBPF 기반 로드밸런싱
    (ClusterIP 로드 밸런싱을 eBPF 기반으로 지원. GKE v1.15 이상 또는 Linux 커널 4.19 이상을 실행하는 경우, 모든 NodePort/ExternalIP/HostPort 서비스 타입도 eBPF 기반으로 처리)
  • 네트워크 정책 및 가시성 지원
    (eBPF 기반 NetworkPolicy 및 가시성 지원)

Google Cloud에서 Cilium을 실행할 경우, 네이티브 라우팅 구성 으로 실행되는 Cilium과 함께 Google Cloud의 네트워킹 계층을 활용할 수 있습니다. Alias IP를 통해 캡슐화 오버헤드 없이 파드 간 직접 통신이 가능하며, eBPF 기반으로 모든 네트워킹 기능이 처리됩니다.

 

Cilium Masquerading

 

pod IP 들은 사설 주소이기 때문에 공개적으로 라우팅 불가합니다. 이런 상황에서 클러스터 외부로 나가려면 모든 트래픽의 출발지 IP 들을 노드의 IP 로 변경할 필요가 있습니다. (노드 IP 는 네트워크에서 라우팅이 가능한 주소이기 때문) SNAT 의 한 형태라고 볼 수 있습니다.

Q. SNAT 과 Masquerading 의 차이점은?
A. 두 가지 방식 모두 출발지 주소를 변경하는 것은 동일하지만, SNAT 은 정적으로 고정 IP 로 변경하고 Masquerading 은 인터페이스에 들어오는 패킷의 IP 를 동적으로 확인하여 변경합니다. IP 주소를 변경하는 시점이 다릅니다.
고정 IP 환경에서는 SNAT 가 더 효율적이고, 동적 IP 환경에서는 Masquerading 이 필수적입니다.

 

필수적으로 보이지만, pod IP 까지 라우팅이 가능한 경우 아래와 같이 Masquerading 을 비활성화 할 수 있습니다.

enable-ipv4-masquerade: false  # IPv4 마스커레이딩 비활성화
enable-ipv6-masquerade: false  # IPv6 마스커레이딩 비활성화

구성 방법

ipv4-native-routing-cidr: 10.0.0.0/8    # IPv4용
ipv6-native-routing-cidr: fd00::/100     # IPv6용

 

앞서 설명했듯이 "파드와 파드 간 통신 시 NAT 없이 통신이 가능해야한다" CNI 요구사항에 따라 pod 대역대는 Masquerading 하지 않습니다. 따라서 pod 대역대는 ip?-native-routing-cidr 옵션을 통해 변환 제외합니다.

예를 들어, Pod 들이 10.244.1.0/24 범위의 IP를 사용한다면, 같은 범위 내의 다른 Pod 들로 가는 트래픽은 변환하지 않습니다. 즉 Pod A(10.244.1.10)에서 Pod B(10.244.1.20)로 통신할 때는 Pod IP 주소를 그대로 유지합니다.

 

 

구현 방법

Cilium Masquerading 은 eBPF 기반 구현 방식과 iptables 구현 방식이 존재합니다.

eBPF 기반 구현

활성화 설정

bpf.masquerade: true  # eBPF 마스커레이딩 활성화

 

특징

  • 고성능: 가장 효율적인 구현 방식
  • BPF Host-Routing: 기본적으로 BPF 호스트 라우팅 모드 활성화
  • NodePort 의존성: 현재 BPF NodePort 기능에 의존 (향후 제거 예정)
  • 디바이스 감지: NodePort 디바이스 감지 메커니즘으로 자동 장치 선택

지원 프로토콜

  • TCP/UDP: 완전 지원
  • ICMP: Echo request/reply, Destination unreachable 제한 지원

상태 확인

kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep Masquerading
# 출력 예: Masquerading: BPF (ip-masq-agent) [eth0, eth1] 10.0.0.0/16

 

eBPF 구현은 커널 수준에서 최적화되어 최고 성능을 제공하지만, IPv6는 베타 상태입니다. masquerading 프로그램이 실행되는 디바이스에서만 동작하며, 클러스터 노드의 External IP 로의 트래픽은 masquerading 하지않는 특징이 있습니다.

ip-masq-agent 통합

활성화 설정

ipMasqAgent.enabled: true

 

구성 옵션

  • nonMasqueradeCIDRs: 마스커레이딩 제외 CIDR 목록
  • masqLinkLocal: 링크 로컬 주소 마스커레이딩 여부
  • masqLinkLocalIPv6: IPv6 링크 로컬 주소 마스커레이딩 여부

기본 제외 CIDR

10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10
192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 198.18.0.0/15
198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4
169.254.0.0/16 (링크 로컬), fe80::/10 (IPv6 링크 로컬)

 

ConfigMap 예시

apiVersion: v1
kind: ConfigMap
metadata:
  name: ip-masq-agent
data:
  config: |
    nonMasqueradeCIDRs:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    masqLinkLocal: true

 

ip-masq-agent 는 세밀한 masquerading 제어를 제공하며, Fsnotify 를 통해 설정 파일 변경을 실시간으로 추적합니다. 기본적으로 사설 주소와 링크 로컬 주소는 masquerading 에서 제외됩니다.

iptables 기반 구현

특징

  • 레거시 구현: 모든 커널 버전에서 동작
  • 기본 동작: Cilium이 아닌 네트워크 디바이스로 나가는 모든 트래픽 마스커레이딩
  • 인터페이스 제한: egress-masquerade-interfaces 옵션으로 특정 인터페이스만 masquerading 지정 가능
    인터페이스 우선순위
      - egress-masquerade-interfaces 설정 시: 해당 인터페이스 우선 사용
      - 미설정 시: devices 필드의 인터페이스 사용
      - 접두사 매칭: eth+ ens+ 형태로 여러 인터페이스 패턴 지정 가능

iptables 기반은 레거시한 모드로 권장하지 않습니다.

 

반응형

'인프라 > 쿠버네티스' 카테고리의 다른 글

[cilium] cilium 의 LoadBalancer 서비스  (2) 2025.08.09
[cilium] Overlay Network  (1) 2025.08.09
[cilium] IPAM  (3) 2025.07.31
[cilium] Hubble Exporter  (2) 2025.07.23
[cilium] Hubble 개념과 설치  (5) 2025.07.21

+ Recent posts