Kubernetes

AEWS [3기] 2주차 - EKS Networking (iptables 와 NetFilter)

yu3papa 2025. 2. 13. 10:00

컨테이너 및 쿠버네티스 네트워킹을 이해하기 위해서는 iptables 규칙 이해가 필수입니다.

iptables 규칙을 이해하기 전에 리눅스 네트워킹을 먼저 이해해 보겠습니다.

1. 리눅스 네트워킹

네트워킹의 정의는 여러가지가 있겠지만, IT 시스템에서의 네트워킹이란

"한 노드(송신자)에 다른 노드(수신자)로 정보를 전달하는것" 이라고 정의할 수 있습니다.

여기에서 노드는 PC, 서버, 스마트폰, IoT 장비등일것입니다.

위와 같은 노드들은 다양한 하드웨어로 설계되어 있고, 네트워킹 또한 제 각각이었습니다.

이러한 다양한  하드웨어 및 소프트웨어 기술이 지리적, 정치적 경계를 넘어 일관되게 작동해야 하기 때문에 네트워크를 통해 데이터를 전송하는 것은 복잡합니다. 

이러한 문제를 해결하기위해 ISO에서는 OSI(Open Systems Interconnection (OSI) model) 네트워깅 레퍼런스 모델을 제시합니다. OSI 데이터 모델은 컴퓨터 네트워킹을 위한 범용 언어를 제공하기 때문에 다양한 기술이 표준 프로토콜 또는 통신 규칙을 사용하여 통신할 수 있습니다. 계층별로 모든 기술은 특정 기능을 제공하고 해당 기능을 수행해야 네트워킹에 유용하게 사용할 수 있습니다. 상위 계층의 기술은 기본 구현 세부 사항에 대해 걱정할 필요 없이 하위 수준 기술을 사용할 수 있으므로 추상화의 이점을 누릴 수 있습니다.

https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=pst8627&logNo=221670903384&photoView=4

 

https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=pst8627&logNo=221670903384&photoView=4

 

위 그림에서 눈여겨 봐야하는것은 데이터 전송 단위인 Segment, Packet, Frame 입니다. 아래에서 더 자세히 알아보겠습니다.

 

과거에는 SPX/IPX(Sequenced Packet Exchange/Internet Packet Exchange) 및 NetBIOS(Network Basic Input Output System) 같은 다양한 네트워킹 모델이 사용되었으며, 현재는 TCP/IP 모델이 OSI(Open Systems Interconnection) 모델의 대안으로 주로 사용되고 있습니다.

 

1.1. TCP/IP 모델

TCP/IP 모델은 다음의 5개 계층으로 구성됩니다.

 

  • 물리 계층
  • 데이터 링크 계층
  • 네트워크 계층
  • 전송 계층
  • 애플리케이션 계층

https://docs.tigera.io/calico/latest/about/kubernetes-training/about-networking

 

물리 계층, 네트워크 계층, 애플리케이션 계층 등이 OSI 모델에 직접 매핑되는 것처럼 보이지만 실제로는 그렇지 않습니다. 그보다는 TCP/IP 모델이 인터넷의 구조 및 프로토콜에 가장 정확하게 매핑됩니다.

OSI 모델도 교육 목적의 전체론적 관점에서 네트워킹의 작동 방식을 설명하는 네트워킹 모델로 많이 사용되지만 이제는 TCP/IP 모델이 더 많이 사용됩니다.

모든 인터넷 기반 시스템 및 애플리케이션이 TCP/IP 모델 또는 OSI 모델을 따르는 것은 아니라는 점에 유의해야 합니다. 마찬가지로 모든 오프라인 기반 네트워크 시스템 및 애플리케이션이 OSI 모델이나 다른 모델을 사용하는 것도 아닙니다.

OSI 모델과 TCP/IP 모델 모두 개방형 표준입니다. 누구나 사용할 수 있고 추가로 구축하여 특정 요구 사항을 충족할 수 있도록 설계되었습니다.

어떤 조직은 프로토콜과 모델을 포함하여 자체 시스템 내에서만 사용할 수 있는 비공개 소스의 자체 내부 독점 표준을 설계하기도 하고, 나중에 상호 운용성 및 추가 커뮤니티 개발을 위해 일반에 표준을 공개하는 경우도 있습니다. 한 예로 s2n-tls라는 TLS 프로토콜은 원래 Amazon Web Services(AWS)의 독점 프로토콜이었지만 지금은 오픈 소스로 사용되고 있습니다.

1.2. 네트워크 통신 패킷 분석

간단한 샘플 프로그램을 이용하여 client  와 Server 간 패킷 분석을 직접해 보면서 리눅스 네트워킹을 좀 더 심도 있게 알아가 보겠습니다.

자바기반의 스프링부트를 이용하여 아래와 같은 응답을 받는 간단한 REST API를 구성하였습니다.

{
  "appid": 1111,
  "page": "HOME",
  "message": "Hello Envoy!"
}

컴파일된 jar 파일은 아래 링크에서 다운 가능합니다.

주요소스 - SampleController.java

package com.jadecross.envoy.sample;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController implements ApplicationListener<ServletWebServerInitializedEvent> {
    private int portNo;
    @GetMapping("/")
    @ResponseBody
    public ResponseMsg Home() {
        return new ResponseMsg(portNo, "HOME", "Hello Envoy!");
    }
    public void onApplicationEvent(ServletWebServerInitializedEvent servletWebServerInitializedEvent) {
        ServletWebServerApplicationContext applicationContext = servletWebServerInitializedEvent
                .getApplicationContext();
        portNo = applicationContext.getWebServer().getPort();
    }
}

 

주요소스 - ResponseMsg.java

package com.jadecross.envoy.sample;
public class ResponseMsg {
    private int appid;
    private String page;
    private String message;
    public ResponseMsg(int appid, String page, String message) {
        super();
        this.appid = appid;
        this.page = page;
        this.message = message;
    }
}

 

server1 장비에서 어플리케이션을 실행하고

# 어플리케이션 실행
java -jar EnvoySample.jar --server.port=1111
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.5)
...(생략)...
...
2025-02-13 11:54:07.339  INFO 2324 --- [ main] com.jadecross.envoy.sample.Application   : Started Application in 2.087 seconds (JVM running for 2.577)

 

브라우저를 이용하여 http://172.31.0.11:1111 로 접속 후 응답을 확인하면 아래와 같은 결과를 볼수 있습니다.

 

위 과정의 패킷을 수집하였고 와이어샤크로 패킷 분석하면서 네트워크 관련 각 레이어를 분석해 보겠습니다.

sample.pcapng
0.01MB

 

 

1.2.1. Application Layer

  • OSI 7 Layer의 L5~L7에 해당하며, 
    • 응용 계층(Application layer)은 응용 프로세스와 직접 관계하여 일반적인 응용 서비스를 수행한다. 일반적인 응용 서비스는 관련된 응용 프로세스들 사이의 전환을 제공한다. 응용 서비스의 예로, 가상 터미널(예를 들어, 텔넷), "Job transfer and Manipulation protocol" (JTM, 표준 ISO/IEC 8832) 등이 있다.
  • 대표적인 프로토콜
    • HTTP, SMTP, SNMP, FTP, 텔넷, NFS, NTP
  • 대표적인 네트워크 장치
    • L7 스위치

 

1.2.2. Transport  Layer

  • Layer 4에 해당하며,
    • 전송 계층(Transport layer)은 양 끝단(End to end)의 사용자들이 신뢰성있는 데이터를 주고 받을 수 있도록 해 주어, 상위 계층들이 데이터 전달의 유효성이나 효율성을 생각하지 않도록 해준다. 시퀀스 넘버 기반의 오류 제어 방식을 사용한다. 전송 계층은 특정 연결의 유효성을 제어하고, 일부 프로토콜은 상태 개념이 있고(stateful), 연결 기반(connection oriented)이다. 이는 전송 계층이 패킷들의 전송이 유효한지 확인하고 전송 실패한 패킷들을 다시 전송한다는 것을 뜻한다. 가장 잘 알려진 전송 계층의 예는 TCP이다.
  • 대표적인 프로토콜
    • TCP, UDP, RTP, SCTP
  • 대표적인 네트워크 장치
    • L4 스위치

 

1.2.3. Network Layer

  • Layer 3에 해당하며,
    • 네트워크 계층(Network layer)은 여러개의 노드를 거칠때마다 경로를 찾아주는 역할을 하는 계층으로 다양한 길이의 데이터를 네트워크들을 통해 전달하고, 그 과정에서 전송 계층이 요구하는 서비스 품질(QoS)을 제공하기 위한 기능적, 절차적 수단을 제공한다. 네트워크 계층은 라우팅, 흐름 제어, 세그멘테이션(segmentation/desegmentation), 오류 제어, 인터네트워킹(Internetworking) 등을 수행한다. 라우터가 이 계층에서 동작하고 이 계층에서 동작하는 스위치도 있다. 데이터를 연결하는 다른 네트워크를 통해 전달함으로써 인터넷이 가능하게 만드는 계층이다. 논리적인 주소 구조(IP), 곧 네트워크 관리자가 직접 주소를 할당하는 구조를 가지며, 계층적(hierarchical)이다.
  • 대표적인 프로토콜
    • IP, ICMP, IPsec, ARP, RIP, BGP
  • 대표적인 네트워크 장치
    • L3 스위치 (라우터)

 

1.2.4. Data Link Layer

  • Layer 2에 해당하며,
    • 데이터 링크 계층(Data[3]link layer)은 포인트 투 포인트(Point to Point) 간 신뢰성있는 전송을 보장하기 위한 계층으로 CRC 기반의 오류 제어와 흐름 제어가 필요하다. 네트워크 위의 개체들 간 데이터를 전달하고, 물리 계층에서 발생할 수 있는 오류를 찾아 내고, 수정하는 데 필요한 기능적, 절차적 수단을 제공한다. 주소 값은 물리적으로 할당 받는데, 이는 네트워크 카드가 만들어질 때부터 맥 주소(MAC address)가 정해져 있다는 뜻이다. 주소 체계는 계층이 없는 단일 구조이다. 데이터 링크 계층의 가장 잘 알려진 예는 이더넷이다. 이 외에도 HDLC나 ADCCP 같은 포인트 투 포인트(point-to-point) 프로토콜이나 패킷 스위칭 네트워크나 LLC, ALOHA 같은 근거리 네트워크용 프로토콜이 있다. 네트워크 브릿지나 스위치 등이 이 계층에서 동작하며, 직접 이어진 곳에만 연결할 수 있다.
  • 대표적인 프로토콜
    • 이더넷, 토큰링, FDDI, PPP, HDLC, Q.921, 프레임 릴레이, ATM, Fibre Channel
  • 대표적인 네트워크 장치
    • L2 스위치

 

2. 리눅스 커널의 TCP/IP 스택 구현과 Netfilter 서브시스템

리눅스 운영체제는 TCP/IP 스택 중 Layer1 ~ Layer4 까지 커널에 구현하여 운영되고 있습니다.

<BPF Performance Tools 책 발췌 >



리눅스 커널에는 네트워킹의 전형적인 흐름을 필터링할 수 있는 Netfilter 서브시스템을 구현하고 있습니다.

2.1. Netfiter 서브시스템

 

쉽게 표현하면 TCP/IP 각 레이어마다 Hooking 포인트를 삽입하여 Packet의 흐름을 필터링 또는 라우팅이 가능하게 해주는 커널의 기능입니다.

NetFiter는 5개의 Hook 포인트를 제공하며, 패킷 흐름을 제어할 수 있는 체인으로 구성됩니다.

https://netpple.github.io/2022/netfilter-iptables/

NetFiter는 5개의 Hook 포인트(체인)를 제공하며, 사용자 정의 체인을 추가 할 수 있습니다.

2.2. iptables

iptables는 사용자 어플리케이션으로 NetFilter가 제공하는 5개의 Hook 포인트에 네트워크 정책(필터링, NAT, ...)을 설정하고, 삭제하고, 조회하고 모니터링할 수 있는 프로그램입니다.

"iptables/ip6tables — administration tool for IPv4/IPv6 packet filtering and NAT"

리눅스의 기본 방화벽 데몬인 firewalld 도 내부적으로 NetFilter 프레임웍을 이용하여 패킷을 차단합니다.

실습환경에서는 firewalld 데몬은 중지해 놓은 상태입니다.

# 방화벽 데몬 상태 조회
systemctl status firewalld
○ firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: enabled)
     Active: inactive (dead)
       Docs: man:firewalld(1)

3. iptables를 이용한 패킷 흐름제어

iptables는 filter, nat, mangle, raw, security의 5가지 테이블을 지원하며 각 테이블별로 고유한 Chain을 지정하고 정책을 설정합니다. 필요할 경우 사용자 정의 체인을 정의할 수도 있습니다.

테이블 설명
filter
iplables의 기본 테이블로 패킷 필터링 기능을 담당한다
nat
Network Address Translation . IP 주소 및 포트를 변환하고 관리한다
하나의 공인 IP를 여러 호스트가 사용하고자 할 때 주로 사용한다
mangle
성능 향상을 위한 TOS(Type of Service) 설정과 같이 패킷 데이터를 변경하는 특수 규칙을 적용한다
raw
연결추적(Connection Tracking)을 위한 세부 기능을 제공한다
security
SELinux(보안커널)에서 사용하는 접근제어 규칙을 적용한다

 

그리고 각 체인에는 미리 정의된 테이블이 존재합니다.

 

iptables 정책을 설정하고, 평가하는것은 상당히 복잡합니다. 약간의 끈기가 필요합니다.

예시를 통해서 하나씩 접근해 보겠습니다.

 

3.1. 실습환경 구성

 

샘플어플리케이션을 아래와 같이 4개의 인스턴스를 시작합니다.

--> 4개의 서버 어플리케이션 실행
[root@server1 ~]# java -jar EnvoySample.jar --server.port=1111 > /dev/null 2>&1 &
[root@server1 ~]# java -jar EnvoySample.jar --server.port=2222 > /dev/null 2>&1 &
[root@server1 ~]# java -jar EnvoySample.jar --server.port=3333 > /dev/null 2>&1 &
[root@server1 ~]# java -jar EnvoySample.jar --server.port=4444 > /dev/null 2>&1 &
[1] 2694
[2] 2695
[3] 2696
[4] 2697

 

각 인스턴스의 포트(1111, 2222, 3333, 4444)로 호출하면 아래와 같은 응답을 받습니다.

 

① 1111 포트로 인입되는 패킷 Drop

패킷 필터링은 "filter" 테이블의 INPUT 체인에서 수행합니다.

  • INPUT 체인
    • INPUT 체인은 들어오는 트래픽에 적용되는 규칙 세트입니다.
    • INPUT 체인의 규칙은 컴퓨터 자체로 향하는 트래픽에만 적용된다는 점에 유의하는 것이 중요합니다. 패킷이 다른 컴퓨터로 향하는 것이고 컴퓨터가 리디렉션만 하는 경우 이 패킷은 FORWARD 체인에서 처리됩니다.
  • filter 테이블
    • 패킷을 필터링하는 데 사용되며 기본 테이블입니다.
    • 패킷 필터링 후 다른 체인으로 전달하거나 특수 체인으로 전달할 수 있습니다. --> 이를 TARGET 이라고 합니다.
    • 특수 TARGET
특수 TARGET 설명
ACCEPT means to let the packet through
DROP means to drop the packet on the floor
RETURN means stop traversing this chain and resume at the next rule in the previous (calling) chain.

 

현재 filter 테이블에는 아무런 규칙이 적용되어 있지 않습니다.

--> filter 테이블의 규칙 확인
[root@server1 ~]# iptables --table filter --list --numeric
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

 

1111 포트로 인입되는 패킷 Drop 규칙을 추가합니다.

iptables --table filter \ # fiter 테이블의
         --append INPUT \ # INPUT 체인에
         --protocol tcp \ # tcp 프로토콜로
         --dport 1111 \   # 1111 번 포트로 인입되는 패킷에 대하여
         --jump DROP      # 패킷을 폐기

 

적용된 정책을 확인해 보겠습니다.

[root@server1 ~]# iptables --table filter --list --numeric
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:1111

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

 

이제 외부에서 http://172.31.0.11:1111 로 접속하면 아래와 같은 결과를 받습니다.

C:\> curl http://172.31.0.11:1111
curl: (28) Failed to connect to 172.31.0.11 port 1111 after 21038 ms: Could not connect to server

 

그림으로 요약하면 아래와 같습니다.

 

다음 실습을 위해 적용한 규칙을 삭제하겠습니다.

# 적용한 규칙 삭제 명령
iptables -t filter -D INPUT -p tcp --dport 1111 -j DROP

# 또는 특정 체인의 모든 규칙을 삭제하려면 다음 명령어를 사용합니다.
iptables -F INPUT

# 또는 모든 체인의 모든 규칙을 삭제하려면 다음 명령어를 사용합니다.
iptables -F

# 규칙이 삭제되었는지 확인
iptables --table filter --list --numeric
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

 

② 8080 포트로 들어오는 트래픽을 1111 포트로 전송

현재 실행한 4개의 서버 인스턴스는 8080 포트를 LISTEN  하고 있지 않습니다.

ss | grep 8080
# 아무런 결과가 없습니다.

 

외부 사용자는 http://172.31.0.11:8080 에서 응답을 받을 수 없습니다.

C:\> curl http://172.31.0.11:8080
curl: (7) Failed to connect to 172.31.0.11 port 8080 after 2039 ms: Could not connect to server

 

NAT(Network Address Translation) 작업은 nat 테이블의 PREROUTING 체인에서 수행합니다.

  • PREROUTING 체인
    • iptables의 PREROUTING 체인은 패킷이 수신될 때, 즉 해당 네트워크 스택으로 전송되기 전에 패킷을 처리하는 데 사용됩니다. Linux 시스템에서 들어오는 패킷을  찾을 수 있는 첫 번째 위치입니다.
    • 체인은 커널 수준에서 작동하므로 패킷을 매우 빠르고 효율적으로 처리할 수 있습니다. 그러나 이는 구성 오류로 인해 네트워크 연결에 심각한 문제가 발생할 수 있음을 의미합니다.
  • nat 테이블 (PREROUTING 체인)
    • 패킷은 라우팅 결정을 내리기 전에 이 체인에 들어갑니다.
    • "routing decision"이라는 용어는 트래픽을 incoming(호스트 자체)과 transit(이 호스트를 거쳐 다른 호스트로 이동)로 구분하는 것을 의미합니다. 이 단계에서 전달 작업(DNAT, REDIRECT, NETMAP)을 수행해야 합니다.
    • 전달 액션
액션 설명
DNAT
(Destination NAT)
패킷이 PREROUTING 체인을 통과할 때 패킷의 대상 주소 또는 포트를 변경할 수 있습니다. 결과적으로 패킷은 다른 대상 주소 및/또는 포트로 리디렉션됩니다.

DNAT 사용 예:
# HOST 80 으로 인입되는 패킷을 다른 서버 192.168.1.100:8080 으로 리디렉션
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
REDIRECT 패킷을 동일한 호스트의 로컬 포트로 리디렉션할 수 있으며, 패킷은 PREROUTING 체인을 통과합니다. 로컬 수준에서 프록시 서버 또는 포트 포워딩을 구성할 때 유용합니다.

REDIRECT 사용 예:
# HOST 8080으로 인입되는 패킷을 로컬 80 포트로 리디렉션
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80
NETMAP 특수 카드를 사용하는 네트워크의 패킷 IP 주소를 변경할 수 있는 iptables의 추가 확장입니다. 예를 들어, 서로 다른 개인 네트워크 간에 패킷을 리디렉션할 때 유용합니다.

NETMAP 사용 예:
# HOST 에 NIC가 2개일때 192.168.0.0/24 서브넷에서 인입되는 패킷을 172.16.0.0/24 서브넷을 사용하는 NIC 인터페이스로 리디렉션
iptables -t nat -A PREROUTING -d 192.168.0.0/24 -j NETMAP --to 172.16.0.0/24

 

현재 nat  테이블의 규칙은 아래와 같이 초기화 되어 있는 상태입니다.

[root@server1 ~]# iptables --table nat --list --numeric
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

 

 

8080 포트로 들어오는 트래픽을 1111 포트로 전송하는 규칙을 nat 테이블의 PREROUTING  체인에 추가하겠습니다.

iptables --table nat \                    # nat 테이블의
         --append PREROUTING \            # PREROUTING 체인에 규칙을 추가
         --protocol tcp --dport 8080 \    # tcp 프로토콜 8080 포트로 인입되는 패킷을
         --jump REDIRECT --to-ports 1111  # 호스트의 1111 포트로 리디렉션

 

nat 테이블에 적용된 규칙을 확인해 보겠습니다.

[root@server1 ~]# iptables --table nat --list --numeric
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
REDIRECT   6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 redir ports 1111

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

 

이제 외부 사용자는 http://172.31.0.11:8080 에서 응답을 받을 수 있습니다.

C:\> curl http://172.31.0.11:8080
{"appid":1111,"page":"HOME","message":"Hello Envoy!"}

 

그림으로 도식화 해보면 패킷의 포트가 TCP 레이어에서 변환되는것을 알수 있습니다.

  • Target Port 가 변경되는 부분이 핵심입니다.

 

다음 실습을 위해 nat 테이블의 규칙을 초기화 하고 규칙을 확인합니다.

[root@server1 ~]# iptables --table nat --flush

[root@server1 ~]# iptables --table nat --list --numeric
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

 

도커 컨테이너 실행할 때 --publish 옵션을 사용하면 iptables 의 nat 테이블의 규칙이 내부적으로 적용되게 됩니다.

아래는 도커를 설치 하지 않은곳과 도커를 설치했을 때 iptables 각 테이블의 규칙을 정리한 표입니다.

  • iptable -t <테이블> --list-rules
    • Print all rules in the selected chain.
    • If no chain is selected, all chains are printed like iptables-save.
    • Like every other iptables command, it applies to the specified table (filter is the default).
table 도커 X 도커 O
filter -P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
nat -P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
mangle -P PREROUTING ACCEPT
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
raw -P PREROUTING ACCEPT
-P OUTPUT ACCEPT
-P PREROUTING ACCEPT
-P OUTPUT ACCEPT
security -P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

 

  • iptable -t <테이블> --list --numeric
    • List all rules in the selected chain.  If no chain is selected, all chains are listed. Like every other iptables command, it applies to the specified table (filter is the default), so NAT rules get listed by
      • iptables -t nat -n -L
    • Please note that it is often used with the -n option, in order to avoid long reverse DNS lookups.  
    • It is legal to specify the -Z (zero) option as well, in which case the chain(s) will be atomically listed and zeroed.  
    • The exact output is affected by the other arguments given. The exact rules are suppressed until you use
      • iptables -L -v
table 도커 X 도커 O
filter Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target                    prot opt source               destination
DOCKER-USER               0    --  0.0.0.0/0            0.0.0.0/0
DOCKER-ISOLATION-STAGE-1  0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT                    0    --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
DOCKER                    0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT                    0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT                    0    --  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (1 references)
target     prot opt source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target                    prot opt source               destination
DOCKER-ISOLATION-STAGE-2  0    --  0.0.0.0/0            0.0.0.0/0
RETURN                    0    --  0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target     prot opt source               destination
DROP       0    --  0.0.0.0/0            0.0.0.0/0
RETURN     0    --  0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     0    --  0.0.0.0/0            0.0.0.0/0
nat Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target      prot opt source               destination
MASQUERADE  0    --  172.17.0.0/16        0.0.0.0/0

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     0    --  0.0.0.0/0            0.0.0.0/0
mangle Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
raw Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
security Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

 

nginx 컨테이너를 실행하고 iptables nat 테이블의 규칙을 확인해 보겠습니다.

(도커 데몬이 실행중인 server2에서 실습을 진행하였습니다)

# 외부에서 8888 포트로 nginx 컨테이너의 80 포트 접속
[root@server2 ~]# docker container run -d --publish 8888:80 nginx:1.27.4

# iptables nat 테이블의 규칙 확인
[root@server2 ~]# iptables -t nat --list-rules
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8888 -j DNAT --to-destination 172.17.0.2:80

# iptables nat 테이블의 규칙 확인
[root@server2 ~]# Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  0    --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  6    --  172.17.0.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     0    --  0.0.0.0/0            0.0.0.0/0
DNAT       6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8888 to:172.17.0.2:80

 

③ 8080 포트로 들어오는 트래픽을 1111, 2222, 3333, 4444 포트로 부하 분산

아래와 같은 부하분산을  iptables 규칙으로 구현해 보겠습니다.

nat 테이블에 아래 규칙을 추가합니다.

iptables --table nat \                   # nat 테이블의
         --append PREROUTING \           # PREROUTING 체인에
         --protocol tcp --dport 8080 \   # tcp 8080 을 인입되는 패킷에서 대해서
         --match statistic --mode nth --every 4 --packet 0 \ # 4개의 요청을 순차적으로 N번씩 분배
         --jump REDIRECT --to-ports 1111 # 호스트의 1111 포트로 리디렉션
         
# 2222, 3333, 4444 포트에 대한 규칙은 축약형으로 실행
iptables -t nat -A PREROUTING -p tcp --dport 8080 -m statistic --mode nth --every 4 --packet 1 -j REDIRECT --to-ports 2222
iptables -t nat -A PREROUTING -p tcp --dport 8080 -m statistic --mode nth --every 4 --packet 2 -j REDIRECT --to-ports 3333
iptables -t nat -A PREROUTING -p tcp --dport 8080 -m statistic --mode nth --every 4 --packet 3 -j REDIRECT --to-ports 4444

 

nat 테이블에 적용된 규칙을 확인해 보겠습니다.

[root@server1 ~]# iptables -t nat --list-rules
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A PREROUTING -p tcp -m tcp --dport 8080 -m statistic --mode nth --every 4 --packet 0 -j REDIRECT --to-ports 1111
-A PREROUTING -p tcp -m tcp --dport 8080 -m statistic --mode nth --every 4 --packet 1 -j REDIRECT --to-ports 2222
-A PREROUTING -p tcp -m tcp --dport 8080 -m statistic --mode nth --every 4 --packet 2 -j REDIRECT --to-ports 3333
-A PREROUTING -p tcp -m tcp --dport 8080 -m statistic --mode nth --every 4 --packet 3 -j REDIRECT --to-ports 4444

[root@server1 ~]# iptables -t nat --list --numeric
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
REDIRECT   6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 statistic mode nth every 4 redir ports 1111
REDIRECT   6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 statistic mode nth every 4 packet 1 redir ports 2222
REDIRECT   6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 statistic mode nth every 4 packet 2 redir ports 3333
REDIRECT   6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 statistic mode nth every 4 packet 3 redir ports 4444

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

 

원격에서 http://172.31.0.11:8080 번으로 여러번 호출해 보면 1111, 2222, 3333, 4444 포트로 부하분산이 되는것을 확인할 수 있습니다.

C:\> curl http://172.31.0.11:8080
{"appid":1111,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":2222,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":1111,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":3333,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":2222,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":1111,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":4444,"page":"HOME","message":"Hello Envoy!"}

C:\> curl http://172.31.0.11:8080
{"appid":3333,"page":"HOME","message":"Hello Envoy!"}

 

도식화 해보면 아래와 같습니다.

 

쿠버네티스의 Service 리소스가 위의 예제와 비슷한 형식으로 POD에 부하분산을 하고 있습니다.

 

4. K8S 서비스 리소스 부하 분산 분석

간단한 방명록 샘플웹어플리케이션을 이용하여 분석해 보겠습니다.

 

메인페이지에서 방명록 웹어플리케이션을 호스팅하는 PORT를 출력하여 놓았습니다.

 

* 지금부터의 실습은 K8S 1.31 버전의 클러스터가 구성된 Control-Plane 노드에서 실습하였습니다.

  • K8S 버전 : v1.31.3
  • Calico CNI
  • MetalLB 설치

K8S에서 3개의 Replica를 갖는 Deployment 와 80 Port를 사용하는 ClusterIP 타입의 서비스를 생성합니다.

# 방명록 Deployment 배포
[root@k8s-cp ~]# kubectl create deployment guestbook --image=yu3papa/guestbook_h2:4.0-multiplatform --port=8080 --replicas=3
deployment.apps/guestbook created

# 방명록 Service 배포
[root@k8s-cp ~]# kubectl expose deployment guestbook --type=ClusterIP --name guestbook-http --port=80 --target-port=8080
service/guestbook-http exposed

# POD 목록 및 IP 확인
[root@k8s-cp ~]# kubectl get po -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE   READINESS GATES
guestbook-86c7444879-dcssr   1/1     Running   0          85s   172.16.228.87   k8s-w1   <none>           <none>
guestbook-86c7444879-pb7c6   1/1     Running   0          85s   172.16.46.18    k8s-w2   <none>           <none>
guestbook-86c7444879-szmz2   1/1     Running   0          85s   172.16.228.88   k8s-w1   <none>           <none>

# Service 클러스터 IP 확인
[root@k8s-cp ~]# kubectl get svc guestbook-http
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
guestbook-http   ClusterIP   10.109.26.126   <none>        80/TCP    48s

 

guestbook-http 서비스의 CLUSTER-IP 와 80 포트를 이용하여 브라우저로 접속해 보겠습니다.

 

nat 테이블의 규칙을 확인해 보면 약 128개의 규칙이 존재합니다.

[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
128

 

전체 규칙 목록은 아래와 같습니다.

[root@k8s-cp ~]# iptables --table nat --list-rules
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N KUBE-EXT-CG5I4G2RS3ZVWGLK
-N KUBE-EXT-EDNDUDH2C75GIR6O
-N KUBE-KUBELET-CANARY
-N KUBE-MARK-MASQ
-N KUBE-NODEPORTS
-N KUBE-POSTROUTING
-N KUBE-PROXY-CANARY
-N KUBE-SEP-3WUDHTYF2LRZXU7W
-N KUBE-SEP-74Q6PFUH4CJSNAMH
-N KUBE-SEP-ABH2FT2WWA73GIS6
-N KUBE-SEP-AYL6OCCSSJSLLOII
-N KUBE-SEP-BRSSTZAHJDKIXPS4
-N KUBE-SEP-DVLTHTRG2P7KHLWO
-N KUBE-SEP-FK3A5EJXYJHMQGSQ
-N KUBE-SEP-G4GNUVZFH4F3M5O5
-N KUBE-SEP-KEGEIM34WKV6JFR4
-N KUBE-SEP-PIZPO33Z55S2YJVI
-N KUBE-SEP-QDG5AKUGWYYP3UVQ
-N KUBE-SEP-Y4FY5V7JQ7TMTIGA
-N KUBE-SEP-YSTON2IOCU4P7AI5
-N KUBE-SEP-ZTDDDQJVDQVRCIMX
-N KUBE-SERVICES
-N KUBE-SVC-BSU77P6ECSNDAWQX
-N KUBE-SVC-CG5I4G2RS3ZVWGLK
-N KUBE-SVC-EDNDUDH2C75GIR6O
-N KUBE-SVC-ERIFXISQEP7F7OF4
-N KUBE-SVC-EZYNCFY2F7N6OQA2
-N KUBE-SVC-GZ25SP4UFGF7SAVL
-N KUBE-SVC-JD5MR3NA4I4DYORP
-N KUBE-SVC-NPX46M4PTMTKRN6Y
-N KUBE-SVC-TCOU7JCQXEZGVUNU
-N cali-OUTPUT
-N cali-POSTROUTING
-N cali-PREROUTING
-N cali-fip-dnat
-N cali-fip-snat
-N cali-nat-outgoing
-A PREROUTING -m comment --comment "cali:6gwbT8clXdHdC1b1" -j cali-PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" -j cali-OUTPUT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -m comment --comment "cali:0i8pjzKKPyA34aQD" -j cali-POSTROUTING
-A KUBE-EXT-CG5I4G2RS3ZVWGLK -s 172.16.0.0/16 -m comment --comment "pod traffic for ingress-nginx/ingress-nginx-controller:http external destinations" -j KUBE-SVC-CG5I4G2RS3ZVWGLK
-A KUBE-EXT-CG5I4G2RS3ZVWGLK -m comment --comment "masquerade LOCAL traffic for ingress-nginx/ingress-nginx-controller:http external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ
-A KUBE-EXT-CG5I4G2RS3ZVWGLK -m comment --comment "route LOCAL traffic for ingress-nginx/ingress-nginx-controller:http external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-CG5I4G2RS3ZVWGLK
-A KUBE-EXT-EDNDUDH2C75GIR6O -s 172.16.0.0/16 -m comment --comment "pod traffic for ingress-nginx/ingress-nginx-controller:https external destinations" -j KUBE-SVC-EDNDUDH2C75GIR6O
-A KUBE-EXT-EDNDUDH2C75GIR6O -m comment --comment "masquerade LOCAL traffic for ingress-nginx/ingress-nginx-controller:https external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ
-A KUBE-EXT-EDNDUDH2C75GIR6O -m comment --comment "route LOCAL traffic for ingress-nginx/ingress-nginx-controller:https external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-EDNDUDH2C75GIR6O
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-NODEPORTS -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https" -m tcp --dport 31347 -j KUBE-EXT-EDNDUDH2C75GIR6O
-A KUBE-NODEPORTS -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http" -m tcp --dport 31216 -j KUBE-EXT-CG5I4G2RS3ZVWGLK
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-3WUDHTYF2LRZXU7W -s 192.168.10.50/32 -m comment --comment "default/kubernetes:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-3WUDHTYF2LRZXU7W -p tcp -m comment --comment "default/kubernetes:https" -m tcp -j DNAT --to-destination 192.168.10.50:6443
-A KUBE-SEP-74Q6PFUH4CJSNAMH -s 172.16.46.3/32 -m comment --comment "metallb-system/metallb-webhook-service" -j KUBE-MARK-MASQ
-A KUBE-SEP-74Q6PFUH4CJSNAMH -p tcp -m comment --comment "metallb-system/metallb-webhook-service" -m tcp -j DNAT --to-destination 172.16.46.3:9443
-A KUBE-SEP-ABH2FT2WWA73GIS6 -s 172.16.228.66/32 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-MARK-MASQ
-A KUBE-SEP-ABH2FT2WWA73GIS6 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 172.16.228.66:53
-A KUBE-SEP-AYL6OCCSSJSLLOII -s 172.16.46.4/32 -m comment --comment "ingress-nginx/ingress-nginx-controller:http" -j KUBE-MARK-MASQ
-A KUBE-SEP-AYL6OCCSSJSLLOII -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http" -m tcp -j DNAT --to-destination 172.16.46.4:80
-A KUBE-SEP-BRSSTZAHJDKIXPS4 -s 172.16.228.67/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-BRSSTZAHJDKIXPS4 -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination 172.16.228.67:9153
-A KUBE-SEP-DVLTHTRG2P7KHLWO -s 172.16.228.66/32 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-MARK-MASQ
-A KUBE-SEP-DVLTHTRG2P7KHLWO -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 172.16.228.66:53
-A KUBE-SEP-FK3A5EJXYJHMQGSQ -s 172.16.228.67/32 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-MARK-MASQ
-A KUBE-SEP-FK3A5EJXYJHMQGSQ -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 172.16.228.67:53
-A KUBE-SEP-G4GNUVZFH4F3M5O5 -s 172.16.228.88/32 -m comment --comment "default/guestbook-http" -j KUBE-MARK-MASQ
-A KUBE-SEP-G4GNUVZFH4F3M5O5 -p tcp -m comment --comment "default/guestbook-http" -m tcp -j DNAT --to-destination 172.16.228.88:8080
-A KUBE-SEP-KEGEIM34WKV6JFR4 -s 172.16.228.87/32 -m comment --comment "default/guestbook-http" -j KUBE-MARK-MASQ
-A KUBE-SEP-KEGEIM34WKV6JFR4 -p tcp -m comment --comment "default/guestbook-http" -m tcp -j DNAT --to-destination 172.16.228.87:8080
-A KUBE-SEP-PIZPO33Z55S2YJVI -s 172.16.46.4/32 -m comment --comment "ingress-nginx/ingress-nginx-controller-admission:https-webhook" -j KUBE-MARK-MASQ
-A KUBE-SEP-PIZPO33Z55S2YJVI -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller-admission:https-webhook" -m tcp -j DNAT --to-destination 172.16.46.4:8443
-A KUBE-SEP-QDG5AKUGWYYP3UVQ -s 172.16.46.4/32 -m comment --comment "ingress-nginx/ingress-nginx-controller:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-QDG5AKUGWYYP3UVQ -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https" -m tcp -j DNAT --to-destination 172.16.46.4:443
-A KUBE-SEP-Y4FY5V7JQ7TMTIGA -s 172.16.228.67/32 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-MARK-MASQ
-A KUBE-SEP-Y4FY5V7JQ7TMTIGA -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 172.16.228.67:53
-A KUBE-SEP-YSTON2IOCU4P7AI5 -s 172.16.46.18/32 -m comment --comment "default/guestbook-http" -j KUBE-MARK-MASQ
-A KUBE-SEP-YSTON2IOCU4P7AI5 -p tcp -m comment --comment "default/guestbook-http" -m tcp -j DNAT --to-destination 172.16.46.18:8080
-A KUBE-SEP-ZTDDDQJVDQVRCIMX -s 172.16.228.66/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-ZTDDDQJVDQVRCIMX -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination 172.16.228.66:9153
-A KUBE-SERVICES -d 10.104.215.118/32 -p tcp -m comment --comment "metallb-system/metallb-webhook-service cluster IP" -m tcp --dport 443 -j KUBE-SVC-GZ25SP4UFGF7SAVL
-A KUBE-SERVICES -d 10.106.17.0/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-EDNDUDH2C75GIR6O
-A KUBE-SERVICES -d 192.168.10.200/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https loadbalancer IP" -m tcp --dport 443 -j KUBE-EXT-EDNDUDH2C75GIR6O
-A KUBE-SERVICES -d 10.97.52.127/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller-admission:https-webhook cluster IP" -m tcp --dport 443 -j KUBE-SVC-EZYNCFY2F7N6OQA2
-A KUBE-SERVICES -d 10.109.26.126/32 -p tcp -m comment --comment "default/guestbook-http cluster IP" -m tcp --dport 80 -j KUBE-SVC-BSU77P6ECSNDAWQX
-A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES -d 10.106.17.0/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-CG5I4G2RS3ZVWGLK
-A KUBE-SERVICES -d 192.168.10.200/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-CG5I4G2RS3ZVWGLK
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-BSU77P6ECSNDAWQX ! -s 172.16.0.0/16 -d 10.109.26.126/32 -p tcp -m comment --comment "default/guestbook-http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-BSU77P6ECSNDAWQX -m comment --comment "default/guestbook-http -> 172.16.228.87:8080" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-KEGEIM34WKV6JFR4
-A KUBE-SVC-BSU77P6ECSNDAWQX -m comment --comment "default/guestbook-http -> 172.16.228.88:8080" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-G4GNUVZFH4F3M5O5
-A KUBE-SVC-BSU77P6ECSNDAWQX -m comment --comment "default/guestbook-http -> 172.16.46.18:8080" -j KUBE-SEP-YSTON2IOCU4P7AI5
-A KUBE-SVC-CG5I4G2RS3ZVWGLK ! -s 172.16.0.0/16 -d 10.106.17.0/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-CG5I4G2RS3ZVWGLK -m comment --comment "ingress-nginx/ingress-nginx-controller:http -> 172.16.46.4:80" -j KUBE-SEP-AYL6OCCSSJSLLOII
-A KUBE-SVC-EDNDUDH2C75GIR6O ! -s 172.16.0.0/16 -d 10.106.17.0/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SVC-EDNDUDH2C75GIR6O -m comment --comment "ingress-nginx/ingress-nginx-controller:https -> 172.16.46.4:443" -j KUBE-SEP-QDG5AKUGWYYP3UVQ
-A KUBE-SVC-ERIFXISQEP7F7OF4 ! -s 172.16.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp -> 172.16.228.66:53" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ABH2FT2WWA73GIS6
-A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp -> 172.16.228.67:53" -j KUBE-SEP-FK3A5EJXYJHMQGSQ
-A KUBE-SVC-EZYNCFY2F7N6OQA2 ! -s 172.16.0.0/16 -d 10.97.52.127/32 -p tcp -m comment --comment "ingress-nginx/ingress-nginx-controller-admission:https-webhook cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SVC-EZYNCFY2F7N6OQA2 -m comment --comment "ingress-nginx/ingress-nginx-controller-admission:https-webhook -> 172.16.46.4:8443" -j KUBE-SEP-PIZPO33Z55S2YJVI
-A KUBE-SVC-GZ25SP4UFGF7SAVL ! -s 172.16.0.0/16 -d 10.104.215.118/32 -p tcp -m comment --comment "metallb-system/metallb-webhook-service cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SVC-GZ25SP4UFGF7SAVL -m comment --comment "metallb-system/metallb-webhook-service -> 172.16.46.3:9443" -j KUBE-SEP-74Q6PFUH4CJSNAMH
-A KUBE-SVC-JD5MR3NA4I4DYORP ! -s 172.16.0.0/16 -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 172.16.228.66:9153" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ZTDDDQJVDQVRCIMX
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics -> 172.16.228.67:9153" -j KUBE-SEP-BRSSTZAHJDKIXPS4
-A KUBE-SVC-NPX46M4PTMTKRN6Y ! -s 172.16.0.0/16 -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https -> 192.168.10.50:6443" -j KUBE-SEP-3WUDHTYF2LRZXU7W
-A KUBE-SVC-TCOU7JCQXEZGVUNU ! -s 172.16.0.0/16 -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns -> 172.16.228.66:53" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DVLTHTRG2P7KHLWO
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns -> 172.16.228.67:53" -j KUBE-SEP-Y4FY5V7JQ7TMTIGA
-A cali-OUTPUT -m comment --comment "cali:GBTAv2p5CwevEyJm" -j cali-fip-dnat
-A cali-POSTROUTING -m comment --comment "cali:Z-c7XtVd2Bq7s_hA" -j cali-fip-snat
-A cali-POSTROUTING -m comment --comment "cali:nYKhEzDlr11Jccal" -j cali-nat-outgoing
-A cali-POSTROUTING -o tunl0 -m comment --comment "cali:SXWvdsbh4Mw7wOln" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully
-A cali-PREROUTING -m comment --comment "cali:r6XmIziWUJsdOK6Z" -j cali-fip-dnat
-A cali-nat-outgoing -m comment --comment "cali:flqWnvo8yq4ULQLa" -m set --match-set cali40masq-ipam-pools src -m set ! --match-set cali40all-ipam-pools dst -j MASQUERADE --random-fully

 

4.1. PREROUTING 체인 규칙 분석

K8S 노드중 한곳에서 PREROUTING 체인 규칙 확인해 보겠습니다.

[root@k8s-cp ~]# iptables --table nat --list-rules PREROUTING
-P PREROUTING ACCEPT
-A PREROUTING -m comment --comment "cali:6gwbT8clXdHdC1b1" -j cali-PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
  • -P PREROUTING ACCEPT
    • -P 옵션은 해당 체인의 기본 정책을 의미합니다.
    • PREROUTING 체인의 기본 정책이 ACCEPT로 설정되어 있어, 특별한 규칙에 해당하지 않는 패킷은 기본적으로 허용됩니다.
  • -A PREROUTING -m comment --comment "cali:6gwbT8clXdHdC1b1" -j cali-PREROUTING
    • -A PREROUTING : PREROUTING 체인에 규칙을 추가함.
    • -m comment --comment "cali:6gwbT8clXdHdC1b1" : 주석을 추가하여 사람이 알아보기 쉽게 함.
    • -j cali-PREROUTING : 패킷을 cali-PREROUTING 체인으로 전달.
      • cali-PREROUTING은 Calico 네트워크 플러그인에서 사용되는 규칙입니다.
      • Calico는 Kubernetes에서 네트워크 정책을 적용하는 데 사용되며, 이 규칙은 Kubernetes Pod 간 트래픽을 제어하기 위한 것입니다.

cali-PREROUTING 체인 규칙을 확인해 보겠습니다.

[root@k8s-cp ~]# iptables --table nat --list cali-PREROUTING --numeric --verbose
Chain cali-PREROUTING (1 references)
target         prot opt source               destination
cali-fip-dnat  0    --  0.0.0.0/0            0.0.0.0/0            /* cali:r6XmIziWUJsdOK6Z */

 

  • -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    • -A PREROUTING : PREROUTING 체인에 규칙을 추가함.
    • -m comment --comment "kubernetes service portals" : 주석 추가 (Kubernetes 서비스 관련 규칙임을 명시).
    • -j KUBE-SERVICES : 패킷을 KUBE-SERVICES 체인으로 전달.
      • 이 규칙은 Kubernetes의 kube-proxy가 관리하는 NAT 테이블 규칙을 나타냅니다.
      • Kubernetes 서비스(ClusterIP, NodePort, LoadBalancer 등)로 들어오는 트래픽을 올바른 엔드포인트(Pod)로 전달하는 역할을 합니다.

KUBE-SERVICES 체인 규칙을 확인해 보겠습니다.

[root@k8s-cp ~]# iptables --table nat --list KUBE-SERVICES --numeric
Chain KUBE-SERVICES (2 references)
target                     prot opt source     destination     
KUBE-SVC-GZ25SP4UFGF7SAVL  6    --  0.0.0.0/0  10.104.215.118  tcp dpt:443                   /* metallb-system/metallb-webhook-service cluster IP */
KUBE-SVC-EDNDUDH2C75GIR6O  6    --  0.0.0.0/0  10.106.17.0     tcp dpt:443                   /* ingress-nginx/ingress-nginx-controller:https cluster IP */
KUBE-EXT-EDNDUDH2C75GIR6O  6    --  0.0.0.0/0  192.168.10.200  tcp dpt:443                   /* ingress-nginx/ingress-nginx-controller:https loadbalancer IP */
KUBE-SVC-EZYNCFY2F7N6OQA2  6    --  0.0.0.0/0  10.97.52.127    tcp dpt:443                   /* ingress-nginx/ingress-nginx-controller-admission:https-webhook cluster IP */
KUBE-SVC-BSU77P6ECSNDAWQX  6    --  0.0.0.0/0  10.109.26.126   tcp dpt:80                    /* default/guestbook-http cluster IP */
KUBE-SVC-NPX46M4PTMTKRN6Y  6    --  0.0.0.0/0  10.96.0.1       tcp dpt:443                   /* default/kubernetes:https cluster IP */
KUBE-SVC-TCOU7JCQXEZGVUNU  17   --  0.0.0.0/0  10.96.0.10      udp dpt:53                    /* kube-system/kube-dns:dns cluster IP */
KUBE-SVC-CG5I4G2RS3ZVWGLK  6    --  0.0.0.0/0  10.106.17.0     tcp dpt:80                    /* ingress-nginx/ingress-nginx-controller:http cluster IP */
KUBE-EXT-CG5I4G2RS3ZVWGLK  6    --  0.0.0.0/0  192.168.10.200  cp dpt:80                     /* ingress-nginx/ingress-nginx-controller:http loadbalancer IP */
KUBE-SVC-ERIFXISQEP7F7OF4  6    --  0.0.0.0/0  10.96.0.10      tcp dpt:53                    /* kube-system/kube-dns:dns-tcp cluster IP */
KUBE-SVC-JD5MR3NA4I4DYORP  6    --  0.0.0.0/0  10.96.0.10      tcp dpt:9153                  /* kube-system/kube-dns:metrics cluster IP */
KUBE-NODEPORTS             0    --  0.0.0.0/0  0.0.0.0/0       ADDRTYPE match dst-type LOCAL /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */

위 결과를 해석해보면 Service 리소스의 ClusterIP(Port)에 매칭되면 KUBE-SVC-YYY 로 전달되는것을 알 수 있습니다.

 

4.2. KUBE-SERVICES-YYY 체인 규칙 분석

방명록 guestbook-http 서비스 체인인 KUBE-SVC-BSU77P6ECSNDAWQX 체인의 규칙을 확인합니다.

[root@k8s-cp ~]# iptables --table nat --list KUBE-SVC-BSU77P6ECSNDAWQX --numeric
Chain KUBE-SVC-BSU77P6ECSNDAWQX (1 references)
target                     prot opt source         destination
KUBE-MARK-MASQ             6    -- !172.16.0.0/16  10.109.26.126  tcp dpt:80                                      /* default/guestbook-http cluster IP */
KUBE-SEP-KEGEIM34WKV6JFR4  0    --  0.0.0.0/0      0.0.0.0/0      statistic mode random probability 0.33333333349 /* default/guestbook-http -> 172.16.228.87:8080 */
KUBE-SEP-G4GNUVZFH4F3M5O5  0    --  0.0.0.0/0      0.0.0.0/0      statistic mode random probability 0.50000000000 /* default/guestbook-http -> 172.16.228.88:8080 */
KUBE-SEP-YSTON2IOCU4P7AI5  0    --  0.0.0.0/0      0.0.0.0/0                                                      /* default/guestbook-http -> 172.16.46.18:8080 */

KUBE-SVC-<YYY> 규칙은 랜덤 확률(현재 3개의 SEP 가 있으니, 대략 33%)로 KUBE-SEP-ZZZ 로 부하분산 처리됨이 확인됩니다.

  • SEP(Service Endpoint) 는 쉽게 말해 파드의 IP와 Port 를 말함, 즉 KUBE-SEP-<ZZZ> 로 전달됨

 

4.3. KUBE-SEP-ZZZ 체인 규칙 분석

방명록 POD 3개로 DNAT(Destination NAT) 되는 KUBE-SEP-ZZZ 체인의 규칙을 확인합니다.

[root@k8s-cp ~]# iptables --table nat --list KUBE-SEP-KEGEIM34WKV6JFR4 --numeric
Chain KUBE-SEP-KEGEIM34WKV6JFR4 (1 references)
target          prot opt source               destination
KUBE-MARK-MASQ  0    --  172.16.228.87        0.0.0.0/0                                /* default/guestbook-http */
DNAT            6    --  0.0.0.0/0            0.0.0.0/0     tcp to:172.16.228.87:8080  /* default/guestbook-http */

[root@k8s-cp ~]# iptables --table nat --list KUBE-SEP-G4GNUVZFH4F3M5O5 --numeric
Chain KUBE-SEP-G4GNUVZFH4F3M5O5 (1 references)
target         prot opt source               destination
KUBE-MARK-MASQ  0    --  172.16.228.88        0.0.0.0/0                                /* default/guestbook-http */
DNAT            6    --  0.0.0.0/0            0.0.0.0/0     tcp to:172.16.228.88:8080  /* default/guestbook-http */

[root@k8s-cp ~]# iptables --table nat --list KUBE-SEP-YSTON2IOCU4P7AI5 --numeric
Chain KUBE-SEP-YSTON2IOCU4P7AI5 (1 references)
target          prot opt source               destination
KUBE-MARK-MASQ  0    --  172.16.46.18         0.0.0.0/0                                /* default/guestbook-http */
DNAT            6    --  0.0.0.0/0            0.0.0.0/0     tcp to:172.16.46.18:8080   /* default/guestbook-http */

위 결과를 분석해 보면 각 POD의 IP:PORT 로 DNAT 처리하여 전달되는것을 확인할 수 있습니다.

 

쿠버네티스의 기본 Service는 정확하게 균등 부하분산 되지 않고 랜덤 확률로 부하 분산이 됩니다.

“guestbook-http” Service의 CLUSTER-IP를 100번 호출하여 POD-IP 로 부하분산을 확인해 보겠습니다.

[root@k8s-cp ~]# for i in {1..100}; do
                  curl -s http://10.109.26.126 | grep IP | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+’
               done
172.16.228.87
172.16.228.88
...(생략)...
172.16.46.18
172.16.46.18

 

위 결과를 차트로 표현하면 아래와 같습니다.

 

5. iptables의 문제점과 대안

K8S 에서 1개의 Service 가 추가될 때 마다 전달되는 "POD의 개수 + α " 만큼의 체인이 늘어나게 됩니다.

아래와 같이 방명록 Service 5개를 더 추가하고 체인 개수의 변화를 알아 보겠습니다.

--> Service 1개일 때 체인 개수 확인
[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
128

--> Service 추가 : Service 2개일 때 체인 개수 확인
[root@k8s-cp ~]# kubectl expose deployment guestbook --type=ClusterIP --name guestbook-http2 --port=82 --target-port=8080
service/guestbook-http2 exposed
[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
143

--> Service 추가 : Service 3개일 때 체인 개수 확인
[root@k8s-cp ~]# kubectl expose deployment guestbook --type=ClusterIP --name guestbook-http3 --port=83 --target-port=8080
service/guestbook-http3 exposed
[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
158

--> Service 추가 : Service 4개일 때 체인 개수 확인
[root@k8s-cp ~]# kubectl expose deployment guestbook --type=ClusterIP --name guestbook-http4 --port=84 --target-port=8080
service/guestbook-http4 exposed
[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
173

--> Service 추가 : Service 5개일 때 체인 개수 확인
[root@k8s-cp ~]# kubectl expose deployment guestbook --type=ClusterIP --name guestbook-http5 --port=85 --target-port=8080
service/guestbook-http5 exposed
[root@k8s-cp ~]# iptables --table nat --list-rules | wc -l
188

 

 

체인은 NetFilter  서브시스템에 의해 리눅스 커널에서 빠르게 처리된다고 하지만 체인 개수가 늘어나게 되면 성능 저하를 유발할 수 밖에 없습니다.

 

이러한 iptables의 대안으로 

  1. nftables  사용
    • iptables  다음 버전으로 RedHat 이 주축이 되어 진행하고 있는 프로젝트입니다.
    • 기존 iptables 생태계가 견고하여 nftables 로의 전환이 더디게 진행되고 있는게 현재 상황입니다.
  2. eBPF 기반의 Cilium CNI 사용
    • NetFilter 체인을 평가하는 대신에 커널 레벨에서 격리된 공간에서 빠르게 처리할 수 있는 기술로 현재 주목받고 있는 기술입니다.
    • https://ebpf.io/ko-kr/what-is-ebpf/
 

What is eBPF? An Introduction and Deep Dive into the eBPF Technology

A detailed step by step introduction to the eBPF technology with lots of references for further reading.

ebpf.io

 

Cilium - Cloud Native, eBPF-based Networking, Observability, and Security

Cloud Native, eBPF-based Networking, Observability, and Security

cilium.io