ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [컨테이너] 처음 열어보는 CNI 스펙
    DevOps, 클라우드/Container 2024. 11. 9. 16:52

    CNI 유지성으로 사용하기


    지금까지 여러 도구를 사용하면서 공식 문서를 수 차례 참고하고 열어보았지만, CNI는 그렇게 진지하게 대했던 적은 없었던 것 같다.

     

    Cilium에서 eBPF가 어쨌고, Calico에서 BGP, Bird, Felix 어쨌고, 또 AWS VPC CNI는 또 어떻고...

    이런 제반사항들을 그저 이론으로만 접하고 있었다고 생각한다.

     

    물론 이런 이론조차도 작은 트러블 슈팅에는 도움이 되었지만, 그래도 앞으로는 CNI를 좀 더 유지성하게 써보기 위해서 '조금만 더' 다이브 해보려고 한다.

     

    CNI 스펙


    CNI를 준수하는 컨테이너 오케스트레이션은 꼭 쿠버네티스만 있는 것은 아니다.

    AWS ECS, Apache Mesos도 역시 CNI를 준수하고, Containerd나 CRI-O와 같은 런타임 역시 CNI와 함께 동작할 수 있다.

     

    이런 컨테이너 관리를 위한 CNI 구현체들은 많다.

    Calico, Cilium이나 AWS VPC CNI도 있을 것이고 AWS ECS CNI와 Azure Container Networking 등 수 많은 구현체가 존재한다.

     

    각각이 추구하는 방향이나 구현 방식은 다르더라고 CNI 라는 인터페이스를 준수할 것이다.

    그러니 이 스펙을 먼저 확인해 볼 필요가 있다.

    CNI Docs를 읽어보면서 어떤 요구사항들이 있는지 확인해보자.

     

     

    cni/SPEC.md at main · containernetworking/cni

    Container Network Interface - networking for Linux containers - containernetworking/cni

    github.com

     

     

    CNI 스펙이 결정하는 요소는 다음과 같다.

    1. 관리자가 네트워크 구성을 정의하기 위한 포맷.
    2. 컨테이너 런타임이 네트워크 플러그인에 요청을 보내기 위한 프로토콜.
    3. 제공된 구성을 기반으로 플러그인을 실행하는 절차.
    4. 플러그인이 다른 플러그인에 기능을 위임하는 절차.
    5. 플러그인이 런타임에 결과를 반환하기 위한 데이터 포맷.

    이걸 요약하면 결국 다음과 같다.

    "관리자가 정의한 네트워크 구성을 통해서 컨테이너 런타임이 네트워크 플러그인과 통신하는 프로토콜과 응답 규격을 결정한다."

     

     

    네트워크 구성

    더보기
    {
      "cniVersion": "1.1.0",
      "cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
      "name": "dbnet",
      "plugins": [
        {
          "type": "bridge",
          // plugin specific parameters
          "bridge": "cni0",
          "keyA": ["some more", "plugin specific", "configuration"],
    
          "ipam": {
            "type": "host-local",
            // ipam specific
            "subnet": "10.1.0.0/16",
            "gateway": "10.1.0.1",
            "routes": [
                {"dst": "0.0.0.0/0"}
            ]
          },
          "dns": {
            "nameservers": [ "10.1.0.1" ]
          }
        },
        {
          "type": "tuning",
          "capabilities": {
            "mac": true
          },
          "sysctl": {
            "net.core.somaxconn": "500"
          }
        },
        {
            "type": "portmap",
            "capabilities": {"portMappings": true}
        }
      ]
    }
    속성 명 속성 타입 설명
    cniVersion  string
    • 이 구성 목록과 모든 개별 구성이 준수하는 CNI 스펙의 Semantic Version 2.0.
    • 현재 버전은 1.1.0
    cniVersions  string list
    • 이 구성이 지원하는 모든 CNI 버전의 목록.
    name string
    • 네트워크 이름.
    • 호스트(또는 다른 관리 도메인)의 모든 네트워크 구성에서 고유해야 함.
    • 영문-숫자로 시작해야 하며, 특수문자는 언더바(), 마침표(.), 하이픈(-)을 쓸 수 있다.
    • 파일 경로에서 허용되지 않는 문자를 포함해서는 안 됨.
    disableCheck boolean
    • disableCheck가 true이면, 런타임은 이 네트워크 구성 목록에 대해 CHECK를 호출해서는 안 됨.
    • 이를 통해 관리자는 플러그인 조합이 잘못된 오류를 반환하는 것으로 알려진 경우 CHECK를 방지할 수 있음.
    disableGC boolean
    • disableGC가 true이면, 런타임은 이 네트워크 구성 목록에 대해 GC를 호출해서는 안 됨.
    • 이를 통해 관리자는 가비지 컬렉션이 원치 않는 영향을 미칠 수 있다고 알려진 경우
      • (예: 여러 런타임 간 공유 구성) GC를 방지할 수 있음.
    loadOnlyInlinedPlugins boolean
      • false(기본값)인 경우, 플러그인 구성 객체를 여러 소스에서 집계할 수 있음을 나타냄.
      • 다른 소스에서 집계된 유효한 플러그인 구성 객체는 해당 네트워크 이름의 최종 plugins 목록에 추가되어야 함.
      • true로 설정된 경우, 주 네트워크 구성 이외의 소스에서 집계된 유효한 플러그인 구성 객체는 무시됨을 나타냄.
      • 네트워크 구성에 plugins가 없는 경우, loadOnlyInlinedPlugins를 true로 설정할 수 없음.
    plugins list
    • 플러그인 구성 객체의 목록.
    • 이 키가 인라인 플러그인 객체로 채워지고 loadOnlyInlinedPlugins가 true인 경우, 네트워크의 최종 플러그인 세트는 이 목록의 모든 플러그인 객체와 네트워크와 같은 이름의 형제 폴더에서 로드된 모든 플러그인을 병합한 것으로 구성되어야 함.

     

    위의 예시처럼 CNI 스펙은 표와 같은 속성을 네트워크 구성에서 정의한다.

    실제 구현에 영향을 주는 만큼, 어떤 버전을 사용하는지나 어떤 동작이나 케이스를 지원해야 하는지 명시되어 있다.

     

    특히, GC와 플러그인이 눈에 띈다.

    가비지 컬렉터가 CNI 상에서 사용된다고 생각하지 못했는데, 이 스펙을 보면서 알게 되었다.

    또, 플러그인을 지원하는 것을 볼 수 있는데, 이는 결국 CNI 구현체 단독으로 모든 동작을 하는게 아니라 플러그인에 별도의 동작을 위임하는 것이다.

     

    프로토콜

    CNI 프로토콜은 컨테이너 런타임이 바이너리를 실행하는 방식으로 동작한다.

    CNI는 플러그인 바이너리와 런타임 사이의 프로토콜을 정의하며, 컨테이너의 네트워크 인터페이스를 구성하는 책임을 수행한다.

     

    플러그인은 크게 두 가지 범주로 나뉘는데, 다음과 같은 플러그인이 있다.

    • 인터페이스 플러그인: 컨테이너 내부에 네트워크 인터페이스를 생성하고 이를 통해 네트워크 연결이 가능하도록 보장한다.
    • 체인 플러그인: 이미 생성된 인터페이스의 구성을 조정하며, 필요한 경우 더 많은 인터페이스를 추가로 생성할 수 있다.

     

    런타임은 플러그인에 파라미터와 구성을 전달하는데, 파라미터는 환경 변수로, 구성은 표준 입력(stdin)을 통해 전달된다. 플러그인은 성공 시 표준 출력(stdout)으로 결과를 반환하고, 실패 시 표준 오류(stderr)로 에러를 반환한다. 그 구성 값과 결과는 JSON 형식으로 인코딩된다.

     

    파라미터는 호출에 따라 달라지는 설정을 정의하며, 구성은 특별한 경우를 제외하고는 네트워크에 대해 일정하게 유지된다.

    런타임은 런타임의 네트워킹 도메인에서 플러그인을 실행해야 한다. (대부분의 경우, 이는 루트 네임스페이스 또는 dom0를 의미한다.)

     

    프로토콜의 파라미터는 OS의 환경 변수를 통해 플러그인에 전달되는데, 그 요소들을 살펴보면 다음과 같은 것들이 있다.

    환경 변수 설명
    CNI_COMMAND
    • CNI 동작 명령어
    • 1.0.0를 기준으로, 다음 5개의 동작을 구현한다.
    • ADD, DEL, CHECK, GC, VERSION
    CNI_CONTAINERID
    • 컨테이너 ID.
    • 런타임에 의해 할당된 컨테이너의 고유한 일반 텍스트 식별자이며 비어 있어서는 안 된다.
    • 영문-숫자로 시작해야 하며, 특수문자는 언더바(), 마침표(.), 하이픈(-)을 쓸 수 있다.
    CNI_NETNS
    • 컨테이너의 "격리 도메인"에 대한 레퍼런스.
    • 네트워크 네임스페이스를 사용하는 경우, 네트워크 네임스페이스에 대한 경로로 사용
      • e.g. /run/netns/[nsname]
    CNI_IFNAME
    • 컨테이너 내부에 생성할 인터페이스의 이름.
    • 플러그인이 이 인터페이스 이름을 사용할 수 없는 경우 오류를 반환해야 한다.
    CNI_ARGS
    • 호출 시 사용자가 전달한 추가 인자.
    • 세미콜론으로 구분된 영숫자 key-value 쌍
      • e.g. "FOO=BAR;ABC=123"
    CNI_PATH
    • CNI 플러그인 실행 파일을 검색할 경로 목록
    • 경로는 OS별 목록 구분자로 구분
      • e.g. Linux는 :, Windows는 ;

     

    여기서 두 가지를 조금 더 확인해보자.

     

    우선, CNI_COMMAND 라는 파라미터가 있다.

    요것은 말 그대로 CNI 상에 정의된 동작을 의미한다. ADD, DEL, CHECK, GC, VERSION와 같은 동작이 있다.

    CNI_NETNS, CNI_CONTAINERID,  CNI_IFNAME는 함께 사용되며 컨테이너 별로 고유한 네트워크 정보를 부여하기 위해 사용된다.

     

    CNI 동작을 보면서 살펴보자.

     

    ADD

    • 네트워크 네임스페이스(CNI_NETNS)에 존재하는 컨테이너 내부에 네트워크 인터페이스(CNI_IFNAME)를 생성 혹은 수정
    • 플러그인을 위한 입력이 이전 결과(prevResult)을 포함하는 경우, 이에 대한 핸들링을 해야 한다.
    • 컨테이너에 이미 존재하는 name을 전달받은 경우, 에러 처리
    • 동일한 (CNI_CONTAINERID, CNI_IFNAME) 쌍에 대해서 DEL(삭제) 없이 ADD를 2번 호출하면 안된다.
      • 다른 인터페이스에 대해서만 컨테이너 id를 2번 이상 추가할 수 있다.

    DEL

    • 네트워크 네임스페이스(CNI_NETNS)에 존재하는 컨테이너 내부에 네트워크 인터페이스(CNI_IFNAME)를 삭제 혹은 ADD 동작을 취소
    • 특정 리소스가 누락되어도 DEL 작업을 완료할 수 있어야 한다.
      • IPAM 플러그인 → 컨테이너의 네트워크 네임스페이스가 누락되어도 IP 할당을 해제하고 성공 상태를 반환해야 한다. (IPAM이 필수가 아니라면)
      • DHCP → 컨테이너 네트워크 인터페이스에서 일반적으로 'release' 메시지를 전송하며, 작업 실패 시 에러를 반환하지 않아야 한다.
      • 브리지 플러그인 → DEL 작업을 IPAM 플러그인에 위임하고, 컨테이너 네트워크 네임스페이스나 네트워크 인터페이스가 누락되어도 리소스를 정리해야 한다.
      • 인터페이스나 수정 사항이 없어도 성공 상태를 반환해야 한다.

     

    CHECK

    • 존재하는 컨테이너의 상태를 런타임에 확인한다.

     

    Version

    • json으로 직렬화된 버전 결과 객체를 표준 출력으로 반환한다.

     

    GC

    • 런타임에 Network에 추가될 것이라 예상 집합을 구체화한다.
    • 네트워크 플러그인이 예상 집합에 존재하지 않는 리소스를 삭제한다.
    • DEL의 대안으로 GC를 사용하면 안된다.
      • DEL로 삭제하지 못한 리소스를 GC가 삭제해야 한다.

     

    STATUS

    • 런타임에 네트워크 플러그인의 readiness를 파악한다.
      • ADD 처리 준비가 된 경우 0을 반환하며 종료한다.
      • ADD 요청을 처리할 수 없는 경우, 0이 아닌 반환 코드 함께 종료하고, 표준 출력에 에러를 출력한다.
    • 플러그인이 STATUS에 의존하면 안된다.
      • 순수하게 상태 확인 용으로만 사용해야 한다.
      • STATUS가 에러를 반환해도 다른 작업 **(ADD, DEL 등)**은 그대로 역할을 수행해야 한다.

     

    위의 동작들 중에서 ADDDEL을 보면 결국 컨테이너를 CNI가 관리하는 네트워크 대역에 참여시키고 해제시키는 동작을 정의하는 것이다.

    특히, CNI_CONTAINERID, CNI_IFNAME는 한 쌍으로 튜플처럼 고유하게 관리되는 것을 볼 수 있다.

     

    리눅스 상에서는 네트워크 네임스페이스를 사용해서 컨테이너 격리와 유사하게 네트워크를 격리할 수 있다.

    컨테이너 역시 격리된 하나의 네임스페이스 상에서는 하나의 컨테이너가 중복된 네트워크 인터페이스를 가질 수 없도록 정의하고 있다.

     

    플러그인


    마지막으로, 플러그인에 대해 살펴보자.

    사실 플러그인이라는 것 자체가 CNI에서 관리하는 부수적인 바이너리지만 CNI라는 것에서 중요한 요소라고 생각한다.

     

     

    GitHub - aws/amazon-ecs-cni-plugins: Networking Plugins repository for ECS Task Networking

    Networking Plugins repository for ECS Task Networking - aws/amazon-ecs-cni-plugins

    github.com

     

     

    ECS CNI의 플러그인

    ECS CNI에 존재하는 플러그인을 보면 ecs-bridge, eni, ipam이 있는 것을 볼 수 있다.

    ECS는 AWS 매니지드 서비스이기 때문에 AWS 네트워크와 관련된 서비스에 연결된 플러그인들이 함께 존재한다.

     

    ecs-bridge의 경우에는 veth 페어를 만들고 네트워크 네임스페이스를 브릿지에 연결한다.

    우리가 도커를 사용할 때 보면 docker0-veth 인터페이스 쌍을 통해서 호스트와 호스트 외부의 연결을 수행한다.

     

    ecs 역시 오케스트레이션이기에 kubelet과 같은 에이전트가 존재한다. (ecs-agent)

    ecs-agent는 파게이트 혹은 ec2로 동작하는 태스크를 주시하면서 ecs와 통신한다.

     

    이를 위해서 존재하는게 ecs-bridge 플러그이다.

     

    이 밖에도, eni나 ipam 플러그인을 보면 eni 역시 태스크(혹은 파드)에 부착되면서 ip 대역을 할당받도록 도와준다.

     

     

    GitHub - projectcalico/calico: Cloud native networking and network security

    Cloud native networking and network security. Contribute to projectcalico/calico development by creating an account on GitHub.

    github.com

    Calico 역시 앞서 언급했던 구현체들을 포함하고 있고, 다음의 경로에서 쿠버네티스의 파드 네트워크 동작이나 ipam과 같은 플러그인을 확인할 수 있다.

    /calico/cni-plugin/pkg/k8s/k8s.go
    /calico/cni-plugin/pkg/plugin/plugin.go

     

    더보기
    grep 'github.com/containernetworking/cni' -r ./                                                                                                                                   
    
    .//app-policy/config/cluster/node-config.yaml:        ExecStartPre=/usr/bin/wget -N -P /opt/cni/bin https://github.com/containernetworking/cni/releases/download/v0.5.1/cni-v0.5.1.tgz
    .//go.mod:      github.com/containernetworking/cni v1.2.0
    .//felix/fv/test-workload/test-workload.go:     cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//go.sum:github.com/containernetworking/cni v1.2.0 h1:fEjhlfWwWAXEvlcMQu/i6z8DA0Kbu7EcmR5+zb6cm5I=
    .//go.sum:github.com/containernetworking/cni v1.2.0/go.mod h1:/r+vA/7vrynNfbvSP9g8tIKEoy6win7sALJAw4ZiJks=
    .//cni-plugin/win_tests/calico_cni_k8s_windows_test.go: cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/tests/calico_cni_test.go: cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/tests/calico_cni_ipam_test.go:    "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/tests/calico_cni_k8s_test.go:     cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/internal/pkg/azure/azure.go:      "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/azure/azure_test.go: "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/utils/network_windows.go:    "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/utils/network_windows.go:    cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/internal/pkg/utils/network_linux.go:      cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/internal/pkg/utils/network_linux.go:      "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/utils/utils.go:      "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/utils/utils.go:      cnitypes "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/internal/pkg/utils/utils.go:      cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/internal/pkg/testutils/utils_windows.go:  "github.com/containernetworking/cni/pkg/invoke"
    .//cni-plugin/internal/pkg/testutils/utils_windows.go:  "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/internal/pkg/testutils/utils_windows.go:  "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/internal/pkg/testutils/utils_windows.go:  types020 "github.com/containernetworking/cni/pkg/types/020"
    .//cni-plugin/internal/pkg/testutils/utils_windows.go:  cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/internal/pkg/testutils/utils_linux.go:    "github.com/containernetworking/cni/pkg/invoke"
    .//cni-plugin/internal/pkg/testutils/utils_linux.go:    "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/internal/pkg/testutils/utils_linux.go:    types020 "github.com/containernetworking/cni/pkg/types/020"
    .//cni-plugin/internal/pkg/testutils/utils_linux.go:    cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/README.md:[cni]: https://github.com/containernetworking/cni
    .//cni-plugin/README.md:[cni specification]: https://github.com/containernetworking/cni/blob/master/SPEC.md
    .//cni-plugin/pkg/types/types.go:       "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/pkg/k8s/k8s.go:   "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/k8s/k8s.go:   cnitypes "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/pkg/k8s/k8s.go:   cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/k8s/k8s.go:   // See: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#cni_args for more info.
    .//cni-plugin/pkg/plugin/plugin.go:     "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/plugin/plugin.go:     cnitypes "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/pkg/plugin/plugin.go:     cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/plugin/plugin.go:     cniSpecVersion "github.com/containernetworking/cni/pkg/version"
    .//cni-plugin/pkg/dataplane/dataplane.go:       "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/dataplane/dataplane.go:       cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go:     "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go:     cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/dataplane/linux/dataplane_linux.go:   "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/dataplane/linux/dataplane_linux.go:   cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/dataplane/windows/dataplane_windows.go:       "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/dataplane/windows/dataplane_windows.go:       cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/ipamplugin/ipam_plugin.go:    "github.com/containernetworking/cni/pkg/skel"
    .//cni-plugin/pkg/ipamplugin/ipam_plugin.go:    cnitypes "github.com/containernetworking/cni/pkg/types"
    .//cni-plugin/pkg/ipamplugin/ipam_plugin.go:    cniv1 "github.com/containernetworking/cni/pkg/types/100"
    .//cni-plugin/pkg/ipamplugin/ipam_plugin.go:    cniSpecVersion "github.com/containernetworking/cni/pkg/version"
          •  

    길어서 접어두긴 했는데, calico의 경우에는 k8s, ipam과 데이터 플레인 구현체 외에는 특이한 플러그인을 당장 확인하지는 못했다.

     

    이어서는


    처음에 말한 것 처럼, 조금만 더 다이브 해본 것을 정리한 것이라 내용이 엄청 깊진 않다.

    사실 패킷도 직접 잡아보면서 테스트해볼까 했는데 요건 좀 더 고민해볼까 한다.

     

    다음에는 파드 네트워크까지 확장해서 한번 짧게만 정리해보자.

    댓글

Designed by Tistory.