ping6.net

Docker 및 Kubernetes에서의 IPv6: 컨테이너 네트워킹 가이드

컨테이너화된 애플리케이션을 위한 IPv6 구성. Docker 데몬 설정, Kubernetes 듀얼 스택, CNI 플러그인 및 서비스 노출을 다룹니다.

ping6.net2024년 12월 14일10 min read
IPv6DockerKubernetescontainersnetworkingDevOps

컨테이너 네트워킹은 기본적으로 IPv4를 사용합니다. 2024년에 프로덕션 워크로드를 실행하고 있다면 이것은 문제입니다.

TL;DR - 빠른 요약

핵심 포인트:

  • Docker 및 Kubernetes는 기본적으로 IPv4 전용입니다. IPv6는 명시적인 구성이 필요합니다
  • Docker에는 daemon.json 변경 및 --ipv6 플래그가 있는 사용자 정의 네트워크가 필요합니다
  • Kubernetes 1.23+는 파드 및 서비스 IPv6로 안정적인 듀얼 스택을 지원합니다
  • CNI 플러그인(Calico, Cilium, Flannel)은 듀얼 스택을 다르게 처리합니다

바로가기: Docker 구성 | Kubernetes 듀얼 스택 | CNI 플러그인 | 테스트


컨테이너에서 IPv6가 필요한 이유#

컨테이너는 IPv4 전용일 수 있지만 인터넷의 나머지 부분은 IPv6로 이동하고 있습니다. 모바일 네트워크, ISP, 클라우드 공급자는 IPv6 우선입니다. 컨테이너화된 서비스가 IPv6를 지원하지 않으면 NAT64 게이트웨이를 통해 지연 시간이 추가되거나 더 나쁘게는 고객을 완전히 놓치게 됩니다.

컨테이너 오케스트레이션 플랫폼(Docker, Kubernetes, ECS, Nomad)은 IPv4 시대에 구축되었습니다. IPv6 지원은 나중에 추가 기능으로 등장했습니다. 기본값은 여전히 IPv4 전용 네트워킹을 가정합니다.

IPv6 활성화는 어렵지 않지만 여러 계층에서 명시적인 구성이 필요합니다: 데몬, 네트워크, 컨테이너, 서비스. 한 계층을 놓치면 신비롭게 중단되는 부분 연결이 발생합니다.

Docker IPv6 구성#

Docker 데몬은 기본적으로 IPv6를 활성화하지 않습니다. /etc/docker/daemon.json에서 구성해야 합니다.

데몬 구성#

/etc/docker/daemon.json을 생성하거나 수정하세요:

{
  "ipv6": true,
  "fixed-cidr-v6": "fd00::/80",
  "experimental": false,
  "ip6tables": true
}

분석:

  • "ipv6": true는 IPv6 지원을 활성화합니다
  • "fixed-cidr-v6"는 컨테이너용 서브넷을 설정합니다(ULA 또는 GUA 접두사 사용)
  • "ip6tables": true는 IPv6 방화벽 규칙을 활성화합니다(Docker 20.10+)

전역적으로 라우팅 가능한 주소를 사용하는 프로덕션의 경우 공급자가 할당한 접두사를 사용하세요:

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1234::/64"
}

변경 사항을 적용하려면 Docker를 재시작하세요:

sudo systemctl restart docker

IPv6가 활성화되었는지 확인하세요:

docker network inspect bridge | grep IPv6

"EnableIPv6": true가 표시되어야 합니다.

기본 브리지 네트워크#

기본 브리지 네트워크는 데몬 구성 후에도 자동으로 IPv6를 얻지 못합니다. 사용자 정의 네트워크를 생성하세요:

docker network create --ipv6 \
  --subnet=172.20.0.0/16 \
  --subnet=fd00:dead:beef::/48 \
  mynetwork

이 네트워크에서 컨테이너를 실행하세요:

docker run -d --network mynetwork nginx

이제 컨테이너는 IPv4 및 IPv6 주소를 모두 받습니다.

사용자 정의 네트워크#

사용자 정의 브리지 네트워크는 듀얼 스택을 지원합니다:

docker network create --ipv6 \
  --subnet=10.1.0.0/24 \
  --gateway=10.1.0.1 \
  --subnet=fd00:cafe::/64 \
  --gateway=fd00:cafe::1 \
  appnetwork

이 네트워크의 컨테이너는 IPv6를 통해 통신할 수 있습니다:

# 터미널 1
docker run -it --rm --network appnetwork --name container1 alpine sh
 
# 터미널 2
docker run -it --rm --network appnetwork alpine sh
ping6 container1

IPv6가 활성화되면 Docker의 내장 DNS 리졸버가 컨테이너 이름에 대한 AAAA 레코드를 반환합니다.

IPv6 NAT 및 라우팅#

기본적으로 Docker는 IPv4에는 NAT를 사용하지만 IPv6에는 NAT를 사용하지 않을 수 있습니다. 이는 fixed-cidr-v6 구성에 따라 다릅니다.

ULA 접두사(fd00::/8)를 사용하는 경우 인터넷 액세스를 위해 NAT가 필요합니다:

# IPv6 포워딩 활성화
sudo sysctl -w net.ipv6.conf.all.forwarding=1
 
# 마스커레이드 규칙 추가
sudo ip6tables -t nat -A POSTROUTING -s fd00::/80 ! -o docker0 -j MASQUERADE

GUA 접두사(전역적으로 라우팅 가능)를 사용하는 경우 NAT 없이 직접 라우팅하세요:

# 컨테이너 서브넷에 대한 경로 추가
sudo ip -6 route add 2001:db8:1234::/64 via <docker-host-ipv6>

상위 라우터를 구성하여 컨테이너 서브넷을 Docker 호스트로 라우팅하세요.

Docker Compose IPv6#

Docker Compose는 네트워크 정의에서 명시적인 IPv6 구성이 필요합니다.

예제 docker-compose.yml:

version: '3.8'
 
services:
  web:
    image: nginx
    networks:
      - frontend
    ports:
      - "80:80"
      - "[::]:8080:80"  # IPv6를 명시적으로 바인딩
 
  app:
    image: myapp:latest
    networks:
      - frontend
      - backend
 
  db:
    image: postgres:14
    networks:
      - backend
    environment:
      POSTGRES_HOST_AUTH_METHOD: trust
 
networks:
  frontend:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
        - subnet: fd00:1::/64
          gateway: fd00:1::1
 
  backend:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.21.0.0/16
        - subnet: fd00:2::/64

네트워크마다 enable_ipv6: true 플래그가 필요합니다. IPAM(IP 주소 관리) 구성은 IPv4 및 IPv6 서브넷을 모두 할당합니다.

IPv6용 포트 바인딩 구문:

ports:
  - "80:80"              # IPv4 및 IPv6
  - "0.0.0.0:8080:80"    # IPv4만
  - "[::]:8081:80"       # IPv6만
  - "127.0.0.1:8082:80"  # IPv4 로컬호스트
  - "[::1]:8083:80"      # IPv6 로컬호스트

서비스를 시작하세요:

docker-compose up -d

컨테이너에 IPv6가 있는지 확인하세요:

docker-compose exec web ip -6 addr show

Kubernetes 듀얼 스택#

Kubernetes는 버전 1.21(베타)부터, 1.23(안정)부터 듀얼 스택 네트워킹을 지원합니다.

필수 조건#

  1. Kubernetes 1.23 이상
  2. 듀얼 스택을 지원하는 CNI 플러그인(Calico, Cilium, Flannel, Weave)
  3. 듀얼 스택 모드의 kube-proxy
  4. 클라우드 공급자 지원(LoadBalancer 서비스용)

듀얼 스택 활성화#

새 클러스터의 경우 초기화 중에 듀얼 스택을 활성화하세요. kubeadm 사용:

# kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
  podSubnet: "10.244.0.0/16,fd00:10:244::/56"
  serviceSubnet: "10.96.0.0/16,fd00:10:96::/112"

클러스터를 초기화하세요:

kubeadm init --config kubeadm-config.yaml

관리형 Kubernetes(EKS, GKE, AKS)의 경우 클러스터 생성 중에 듀얼 스택을 활성화하세요:

# EKS
eksctl create cluster \
  --name mycluster \
  --ip-family ipv4,ipv6
 
# GKE
gcloud container clusters create mycluster \
  --enable-ip-alias \
  --stack-type=IPV4_IPV6
 
# AKS
az aks create \
  --resource-group myResourceGroup \
  --name mycluster \
  --network-plugin azure \
  --ip-families IPv4,IPv6

듀얼 스택이 활성화되었는지 확인하세요:

kubectl get nodes -o jsonpath='{.items[*].spec.podCIDRs}'

IPv4 및 IPv6 CIDR이 모두 표시되어야 합니다.

Pod 네트워킹#

Pod는 자동으로 두 패밀리의 주소를 받습니다. 대부분의 경우 특별한 구성이 필요하지 않습니다.

예제 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx

배포하고 주소를 확인하세요:

kubectl apply -f pod.yaml
kubectl get pod test-pod -o jsonpath='{.status.podIPs}'

출력은 IPv4 및 IPv6를 모두 표시합니다:

[{"ip":"10.244.1.5"},{"ip":"fd00:10:244:1::5"}]

Pod 내부의 애플리케이션은 ::(모든 주소) 또는 0.0.0.0에 구체적으로 바인딩해야 합니다:

# Python 예제 - IPv4 및 IPv6 모두에 바인딩
import socket
 
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
sock.bind(("::", 8080))
sock.listen(5)

또는 IPv4용 0.0.0.0과 IPv6용 ::에 별도로 바인딩하세요.

서비스 구성#

서비스는 IPv4 전용, IPv6 전용 또는 듀얼 스택일 수 있습니다. ipFamilyPolicyipFamilies 필드로 제어하세요.

듀얼 스택 서비스(듀얼 스택 클러스터의 기본값):

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ipFamilyPolicy: RequireDualStack
  ipFamilies:
    - IPv4
    - IPv6
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080

IPv4 전용 서비스:

apiVersion: v1
kind: Service
metadata:
  name: myservice-v4
spec:
  ipFamilyPolicy: SingleStack
  ipFamilies:
    - IPv4
  selector:
    app: myapp
  ports:
    - port: 80

IPv6 전용 서비스:

apiVersion: v1
kind: Service
metadata:
  name: myservice-v6
spec:
  ipFamilyPolicy: SingleStack
  ipFamilies:
    - IPv6
  selector:
    app: myapp
  ports:
    - port: 80

서비스 IP를 확인하세요:

kubectl get svc myservice -o jsonpath='{.spec.clusterIPs}'

출력:

["10.96.100.5","fd00:10:96::a5"]

LoadBalancer 서비스#

LoadBalancer 서비스는 듀얼 스택 프론트엔드로 클라우드 로드 밸런서를 프로비저닝합니다(클라우드 공급자가 지원하는 경우).

apiVersion: v1
kind: Service
metadata:
  name: web-lb
spec:
  type: LoadBalancer
  ipFamilyPolicy: RequireDualStack
  ipFamilies:
    - IPv4
    - IPv6
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

외부 IP를 확인하세요:

kubectl get svc web-lb

출력:

NAME     TYPE           CLUSTER-IP      EXTERNAL-IP                        PORT(S)
web-lb   LoadBalancer   10.96.100.10    203.0.113.10,2001:db8:1234::10    80:30123/TCP

모든 클라우드 공급자가 아직 듀얼 스택 로드 밸런서를 지원하는 것은 아닙니다. 플랫폼에 대한 지원을 확인하세요.

Ingress 컨트롤러#

Ingress의 IPv6 지원은 컨트롤러 구현에 따라 다릅니다.

IPv6를 지원하는 인기 있는 컨트롤러:

  • nginx-ingress: 듀얼 스택 지원, IPv4 및 IPv6 모두에서 수신
  • Traefik: 전체 듀얼 스택 지원
  • HAProxy Ingress: IPv6 지원
  • Contour: 듀얼 스택 가능

듀얼 스택용 nginx-ingress 구성:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: LoadBalancer
  ipFamilyPolicy: RequireDualStack
  ipFamilies:
    - IPv4
    - IPv6
  selector:
    app.kubernetes.io/name: ingress-nginx
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https

Ingress 리소스는 특별한 IPv6 구성이 필요하지 않습니다. 컨트롤러가 지원하면 자동으로 작동합니다.

네트워크 정책#

NetworkPolicy 리소스는 IPv6 CIDR 블록을 지원합니다:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ipv6
spec:
  podSelector:
    matchLabels:
      app: myapp
  ingress:
    - from:
      - ipBlock:
          cidr: 2001:db8::/32
      - ipBlock:
          cidr: fd00::/8
  egress:
    - to:
      - ipBlock:
          cidr: ::/0
          except:
            - fc00::/7  # ULA 차단

IPv4 및 IPv6 규칙은 동일한 정책에 공존할 수 있습니다.

CNI 플러그인 고려 사항#

다양한 CNI 플러그인은 듀얼 스택을 다르게 처리합니다.

Calico#

Calico는 IP 풀로 듀얼 스택을 지원합니다:

apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: ipv4-pool
spec:
  cidr: 10.244.0.0/16
  ipipMode: Never
  natOutgoing: true
  nodeSelector: all()
 
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: ipv6-pool
spec:
  cidr: fd00:10:244::/56
  natOutgoing: false
  nodeSelector: all()

Calico에서 IPv6 활성화:

kubectl set env daemonset/calico-node -n kube-system IP6=autodetect
kubectl set env daemonset/calico-node -n kube-system FELIX_IPV6SUPPORT=true

Cilium#

Cilium은 기본 듀얼 스택 지원을 제공합니다. 설치 중에 활성화하세요:

helm install cilium cilium/cilium \
  --namespace kube-system \
  --set ipv4.enabled=true \
  --set ipv6.enabled=true \
  --set tunnel=disabled \
  --set autoDirectNodeRoutes=true

Flannel#

Flannel은 DaemonSet 구성에서 듀얼 스택 모드가 필요합니다:

apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
data:
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "IPv6Network": "fd00:10:244::/56",
      "Backend": {
        "Type": "vxlan"
      }
    }

Weave#

Weave는 두 IPAM 모드로 듀얼 스택을 지원합니다:

kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')&env.IPALLOC_RANGE=10.244.0.0/16&env.IPALLOC_RANGE=fd00:10:244::/56"

일반적인 함정#

애플리케이션 바인딩#

애플리케이션은 명시적으로 IPv6 주소를 수신해야 합니다. 0.0.0.0에 바인딩하면 IPv4만 수신합니다.

잘못된 방법:

server.bind(("0.0.0.0", 8080))  # IPv4만

올바른 방법:

server.bind(("::", 8080))  # IPv6 (그리고 IPV6_V6ONLY=0이면 IPv4도)

많은 언어는 기본적으로 IPv4 전용입니다. 프레임워크 문서를 확인하세요.

DNS 해석#

듀얼 스택 환경의 DNS 쿼리는 A 및 AAAA 레코드를 모두 반환합니다. 애플리케이션은 둘 다 시도하고 IPv6를 선호해야 합니다.

일부 오래된 라이브러리는 A 레코드만 쿼리합니다. 종속성을 업데이트하거나 DNS 해석을 명시적으로 구성하세요.

방화벽 규칙#

컨테이너 방화벽(iptables/ip6tables)은 두 패밀리 모두에 대한 규칙이 필요합니다. Docker와 Kubernetes는 올바르게 구성되면 이를 자동으로 처리하지만 사용자 정의 규칙이 IPv6를 차단할 수 있습니다.

IPv6 방화벽 규칙을 확인하세요:

sudo ip6tables -L -n -v

외부 연결#

IPv6가 있는 컨테이너는 외부 네트워크에 대한 적절한 라우팅이 필요합니다. 호스트에 IPv6 연결이 없으면 컨테이너도 연결할 수 없습니다.

먼저 호스트 IPv6를 테스트하세요:

ping6 google.com

실패하면 컨테이너 문제를 해결하기 전에 호스트 네트워킹을 수정하세요.

StatefulSet 헤드리스 서비스#

헤드리스 서비스가 있는 StatefulSet은 Pod DNS 이름에 대해 IPv4 및 IPv6 주소를 모두 반환합니다:

nslookup web-0.myservice.default.svc.cluster.local

StatefulSet Pod에 연결하는 애플리케이션은 여러 주소를 우아하게 처리해야 합니다.

듀얼 스택 배포 테스트#

테스트 애플리케이션을 배포하세요:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: test-svc
spec:
  type: LoadBalancer
  ipFamilyPolicy: RequireDualStack
  ipFamilies:
    - IPv4
    - IPv6
  selector:
    app: test
  ports:
    - port: 80

적용하고 테스트하세요:

kubectl apply -f test-app.yaml
 
# 서비스 IP 가져오기
kubectl get svc test-svc
 
# IPv4 테스트
curl http://<ipv4-external-ip>
 
# IPv6 테스트
curl -6 http://[<ipv6-external-ip>]

Pod 내부에서 듀얼 스택 연결을 테스트하세요:

kubectl run -it --rm debug --image=alpine --restart=Never -- sh
 
# Pod 내부
apk add curl bind-tools
nslookup test-svc
curl -4 http://test-svc  # IPv4
curl -6 http://test-svc  # IPv6

프로덕션 고려 사항#

  1. 관찰 가능성 도구에서 두 주소 패밀리 모두 모니터링
  2. 한 패밀리를 사용할 수 없을 때 장애 조치 동작 테스트
  3. IPv4 및 IPv6 모두에 대한 상태 확인 구성
  4. IPv6 접두사를 포함한 네트워크 토폴로지 문서화
  5. 충돌을 피하기 위한 IP 주소 할당 계획
  6. 테스트를 위해 CI/CD 파이프라인에서 IPv6 활성화
  7. 듀얼 스택 문제 해결에 대한 팀 교육

관련 글#

컨테이너 연결 확인

Ping 도구IPv6 검증기를 사용하여 컨테이너화된 서비스가 IPv6를 통해 연결 가능한지 테스트하세요.

추가 리소스#