TCP, UDP, SCTP 프로토콜에 대해 IP 주소 또는 포트 수준에서 트래픽 흐름을 제어하려는 경우, 클러스터의 특정 애플리케이션에 쿠버네티스 네트워크폴리시(NetworkPolicy) 사용을 고려할 수 있다. 네트워크폴리시는 애플리케이션 중심의 구조로, 이를 통해 파드가 네트워크상의 다양한 네트워크 "엔티티"와 통신하도록 허용하는 방식을 지정할 수 있다(여기서는 쿠버네티스에서 특정한 의미를 갖는 "엔드포인트"와 "서비스" 같은 일반적인 용어와 혼동되지 않도록 "엔티티"라는 단어를 사용한다). 네트워크폴리시는 한쪽 또는 양쪽 끝이 파드인 연결에 적용되며, 다른 연결에는 관련되지 않는다.
파드가 통신할 수 있는 엔티티는 다음 세 식별자의 조합으로 식별된다.
파드 또는 네임스페이스 기반 네트워크폴리시를 정의할 때는 셀렉터를 사용하여 셀렉터와 일치하는 파드에서 나가거나 해당 파드로 들어오는 트래픽 중 무엇을 허용할지 지정한다.
한편, IP 기반 네트워크폴리시를 생성할 때는 IP 블록(CIDR 범위)을 기반으로 정책을 정의한다.
네트워크 정책은 네트워크 플러그인으로 구현된다. 네트워크 정책을 사용하려면 네트워크폴리시를 지원하는 네트워킹 솔루션을 사용해야 한다. 이를 구현하는 컨트롤러 없이 네트워크폴리시 리소스를 생성하면 아무런 효과가 없다.
파드에는 두 가지 격리가 있다. 이그레스에 대한 격리와 인그레스에 대한 격리이다. 이는 어떤 연결을 수립할 수 있는지와 관련된다. 여기서 "격리"는 절대적인 것이 아니라, "일부 제한이 적용된다"는 뜻이다. 반대로 "$direction 방향으로 비격리"는 명시된 방향에 제한이 적용되지 않는다는 뜻이다. 두 종류의 격리(또는 비격리)는 독립적으로 선언되며, 파드에서 다른 파드로의 연결에는 둘 다 관련된다.
기본적으로 파드는 이그레스에 대해 비격리 상태이며, 모든 아웃바운드 연결이 허용된다.
파드를 선택하면서 policyTypes에 "Egress"를 포함하는 네트워크폴리시가 하나라도 있으면
파드는 이그레스에 대해 격리된다. 이러한 정책은 파드의 이그레스에 적용된다고 말한다.
파드가 이그레스에 대해 격리되면, 파드에서 나가는 연결 중에서
파드의 이그레스에 적용되는 어떤 네트워크폴리시의 egress 목록이 허용하는 연결만
허용된다. 이렇게 허용된 연결의 응답 트래픽도 암묵적으로 허용된다.
이러한 egress 목록의 효과는 합산되어 적용된다.
기본적으로 파드는 인그레스에 대해 비격리 상태이며, 모든 인바운드 연결이 허용된다.
파드를 선택하면서 policyTypes에 "Ingress"를 포함하는 네트워크폴리시가 하나라도 있으면
파드는 인그레스에 대해 격리된다. 이러한 정책은 파드의 인그레스에 적용된다고 말한다.
파드가 인그레스에 대해 격리되면, 파드로 들어오는 연결 중에서
파드의 노드에서 오는 연결과 파드의 인그레스에 적용되는 어떤 네트워크폴리시의 ingress 목록이
허용하는 연결만 허용된다. 이렇게 허용된 연결의 응답 트래픽도 암묵적으로 허용된다.
이러한 ingress 목록의 효과는 합산되어 적용된다.
네트워크 정책은 충돌하지 않는다. 이 정책들은 합산되어 적용된다. 특정 파드의 특정 방향에 하나 이상의 정책이 적용되면, 해당 파드에서 그 방향으로 허용되는 연결은 적용 가능한 정책들이 허용하는 연결의 합집합이다. 따라서 평가 순서는 정책 결과에 영향을 주지 않는다.
소스 파드에서 대상 파드로의 연결이 허용되려면, 소스 파드의 이그레스 정책과 대상 파드의 인그레스 정책이 모두 그 연결을 허용해야 한다. 어느 한쪽이라도 연결을 허용하지 않으면, 연결은 이루어지지 않는다.
리소스의 전체 정의는 네트워크폴리시 레퍼런스를 참고한다.
네트워크폴리시의 예시는 다음과 같다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
필수 필드: 다른 모든 쿠버네티스 구성과 마찬가지로, 네트워크폴리시에는 apiVersion,
kind, metadata 필드가 필요하다. 구성 파일 작업에 대한 일반 정보는
컨피그맵을 사용하도록 파드 구성하기와
쿠버네티스 오브젝트 관리를 참고한다.
spec: 네트워크폴리시 spec은 주어진 네임스페이스에서 특정 네트워크 정책을 정의하는 데 필요한 모든 정보를 담고 있다.
podSelector: 각 네트워크폴리시에는 정책이 적용될 파드 그룹을 선택하는
podSelector가 포함된다. 예시 정책은 "role=db" 레이블이 있는 파드를 선택한다. 비어 있는
podSelector는 네임스페이스의 모든 파드를 선택한다.
policyTypes: 각 네트워크폴리시에는 Ingress, Egress 또는 두 가지 모두를 포함할 수 있는
policyTypes 목록이 포함된다. policyTypes 필드는 주어진 정책이 선택된 파드로 들어오는
인그레스 트래픽, 선택된 파드에서 나가는 이그레스 트래픽 또는 둘 다에 적용되는지를 나타낸다. 네트워크폴리시에
policyTypes가 지정되지 않으면 기본적으로 Ingress는 항상 설정되고,
네트워크폴리시에 이그레스 규칙이 있으면 Egress가 설정된다.
ingress: 각 네트워크폴리시에는 허용되는 ingress 규칙 목록이 포함될 수 있다. 각 규칙은
from과 ports 섹션 모두와 일치하는 트래픽을 허용한다. 예시 정책에는 단일
규칙이 포함되어 있으며, 세 소스 중 하나에서 단일 포트로 들어오는 트래픽과 일치한다. 첫 번째는
ipBlock으로, 두 번째는 namespaceSelector로, 세 번째는 podSelector로 지정된다.
egress: 각 네트워크폴리시에는 허용되는 egress 규칙 목록이 포함될 수 있다. 각 규칙은
to와 ports 섹션 모두와 일치하는 트래픽을 허용한다. 예시 정책에는 단일
규칙이 포함되어 있으며, 10.0.0.0/24의 모든 목적지로 향하는 단일 포트의 트래픽과 일치한다.
따라서 예시 네트워크폴리시는 다음과 같다.
default 네임스페이스의 role=db 파드들을 인그레스와 이그레스 트래픽 모두에 대해 격리한다.
(아직 격리되지 않은 경우)
(인그레스 규칙) 다음 소스에서 TCP 포트 6379로 들어오는, default 네임스페이스에서
role=db 레이블을 가진 모든 파드로의 연결을 허용한다.
default 네임스페이스에서 role=frontend 레이블을 가진 모든 파드project=myproject 레이블을 가진 네임스페이스의 모든 파드172.17.0.0-172.17.0.255와 172.17.2.0-172.17.255.255 범위의 IP 주소
(즉, 172.17.1.0/24를 제외한 172.17.0.0/16 전체)(이그레스 규칙) default 네임스페이스에서 role=db 레이블을 가진 모든 파드가
TCP 포트 5978의 CIDR 10.0.0.0/24로 연결하는 것을 허용한다.
추가 예시는 네트워크 폴리시(Network Policy) 선언하기 연습을 참고한다.
to와 from 셀렉터의 동작ingress의 from 섹션 또는 egress의
to 섹션에는 네 가지 종류의 셀렉터를 지정할 수 있다.
podSelector: 네트워크폴리시와 같은 네임스페이스에 있는 특정 파드를 선택하며, 이 파드는 인그레스 소스 또는 이그레스 목적지로 허용되어야 한다.
namespaceSelector: 모든 파드가 인그레스 소스 또는 이그레스 목적지로 허용되어야 하는 특정 네임스페이스를 선택한다.
namespaceSelector 와 podSelector: namespaceSelector와
podSelector를 모두 지정하는 단일 to/from 항목은 특정 네임스페이스 안의 특정 파드를 선택한다.
올바른 YAML 구문을 사용하도록 주의해야 한다. 예를 들면 다음과 같다.
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
...
이 정책에는 user=alice 레이블이 있는 네임스페이스에서 role=client 레이블을 가진 파드의
연결을 허용하는 단일 from 요소가 포함된다. 하지만 다음 정책은 다르다.
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
...
이 정책은 from 배열에 두 요소를 포함하며, 로컬
네임스페이스에서 role=client 레이블을 가진 파드 또는 임의의 네임스페이스에서
user=alice 레이블을 가진 모든 파드의 연결을 허용한다.
의심스러운 경우, kubectl describe를 사용해서 쿠버네티스가 정책을 어떻게 해석했는지 확인한다.
ipBlock: 인그레스 소스 또는 이그레스 목적지로 허용할 특정 IP CIDR 범위를 선택한다. 파드 IP는 일시적이고 예측할 수 없으므로, 이 범위는 클러스터 외부 IP여야 한다.
클러스터 인그레스 및 이그레스 메커니즘은 종종 패킷의 소스 또는 목적지 IP의
재작성을 필요로 한다. 이런 일이 발생하는 경우, 이것이 네트워크폴리시 처리 전인지
후인지 정의되어 있지 않으며, 네트워크 플러그인, 클라우드 제공자, Service 구현 등의
조합에 따라 동작이 달라질 수 있다.
인그레스의 경우, 어떤 상황에서는 실제 원본 소스 IP를 기준으로 들어오는
패킷을 필터링할 수 있지만, 다른 상황에서는 네트워크폴리시가 동작하는 "소스 IP"가
LoadBalancer의 IP나 파드 노드의 IP 등일 수 있음을 의미한다.
이그레스의 경우, 파드에서 Service IP로 가는 연결이 클러스터 외부 IP로 다시 쓰이면
ipBlock 기반 정책의 적용을 받을 수도 있고 받지 않을 수도 있음을 의미한다.
기본적으로 네임스페이스에 정책이 없으면, 그 네임스페이스의 파드로 들어오고 그 파드에서 나가는 모든 인그레스와 이그레스 트래픽이 허용된다. 다음 예시는 기본 동작을 그 네임스페이스에서 변경할 수 있게 해준다.
모든 파드를 선택하지만 해당 파드로 들어오는 인그레스 트래픽은 허용하지 않는 네트워크폴리시를 생성하여 네임스페이스에 대한 "기본" 인그레스 격리 정책을 생성할 수 있다.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
이렇게 하면 다른 네트워크폴리시에서 선택하지 않은 파드도 여전히 인그레스에 대해 격리된다. 이 정책은 어떤 파드의 이그레스 격리에도 영향을 주지 않는다.
네임스페이스의 모든 파드로 들어오는 모든 연결을 허용하려면, 이를 명시적으로 허용하는 정책을 생성할 수 있다.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
이 정책이 있으면 추가 정책 하나 또는 여러 정책으로도 해당 파드로 들어오는 연결을 거부할 수 없다. 이 정책은 어떤 파드의 이그레스 격리에도 영향을 주지 않는다.
모든 파드를 선택하지만 해당 파드에서 나가는 이그레스 트래픽은 허용하지 않는 네트워크폴리시를 생성하여 네임스페이스에 대한 "기본" 이그레스 격리 정책을 만들 수 있다.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
이렇게 하면 다른 네트워크폴리시에서 선택하지 않은 파드도 이그레스 트래픽이 허용되지 않는다. 이 정책은 어떤 파드의 인그레스 격리 동작도 변경하지 않는다.
한 네임스페이스의 모든 파드로부터의 모든 연결을 허용하려면, 해당 네임스페이스의 파드로부터 나가는 모든 연결을 명시적으로 허용하는 정책을 생성할 수 있다.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
이 정책이 있으면, 추가 정책 하나 또는 여러 정책으로도 해당 파드에서 나가는 연결을 거부할 수 없다. 이 정책은 어떤 파드의 인그레스 격리에도 영향을 주지 않는다.
다음 네트워크폴리시를 해당 네임스페이스에 생성하여 모든 인그레스와 이그레스 트래픽을 차단하는 네임스페이스의 "기본" 정책을 만들 수 있다.
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
이렇게 하면 다른 네트워크폴리시에서 선택하지 않은 파드도 인그레스 또는 이그레스 트래픽이 허용되지 않는다.
네트워크폴리시는 4계층 연결(TCP, UDP, 선택적으로 SCTP)에 대해 정의된다. 그 밖의 모든 프로토콜은 네트워크 플러그인에 따라 동작이 달라질 수 있다.
deny all 네트워크 정책이 정의되면, TCP, UDP, SCTP 연결을 거부하는 것만
보장된다. ARP나 ICMP 같은 다른 프로토콜의 동작은 정의되어 있지 않다.
허용 규칙에도 마찬가지로 적용된다. 특정 파드가 인그레스 소스 또는 이그레스 목적지로 허용될 때,
(예를 들어) ICMP 패킷에 어떤 일이 일어나는지는 정의되어 있지 않다. ICMP 같은 프로토콜은 일부
네트워크 플러그인에서 허용되고 다른 플러그인에서는 거부될 수 있다.
네트워크폴리시를 작성할 때, 단일 포트 대신 포트 범위를 대상으로 지정할 수 있다.
다음 예시처럼 endPort 필드를 사용하여 이를 수행할 수 있다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: multi-port-egress
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 32000
endPort: 32768
위 규칙은 default 네임스페이스에서 role=db 레이블을 가진 모든 파드가
대상 포트가 32000에서 32768 사이인 경우, TCP를 통해
10.0.0.0/24 범위 내의 모든 IP와 통신하도록 허용한다.
이 필드를 사용할 때는 다음 제한 사항이 적용된다.
endPort 필드는 port 필드보다 크거나 같아야 한다.port도 정의된 경우에만 endPort를 정의할 수 있다.endPort 필드를
지원하는 CNI 플러그인을 사용해야 한다.
네트워크 플러그인이
endPort 필드를 지원하지 않는데 이를 포함한 네트워크폴리시를 지정하면,
정책은 단일 port 필드에 대해서만 적용된다.이 시나리오에서는 Egress 네트워크폴리시가 네임스페이스의
레이블 이름을 사용하여 둘 이상의 네임스페이스를 대상으로 한다. 이것이 동작하려면 대상 네임스페이스에 레이블을 지정해야 한다. 예를 들면 다음과 같다.
kubectl label namespace frontend namespace=frontend
kubectl label namespace backend namespace=backend
네트워크폴리시 문서의 namespaceSelector 아래에 레이블을 추가한다. 예를 들면 다음과 같다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-namespaces
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchExpressions:
- key: namespace
operator: In
values: ["frontend", "backend"]
matchLabels 또는 matchExpressions가 있는
namespaceSelector를 사용해야 한다.쿠버네티스 컨트롤 플레인은 모든 네임스페이스에 변경할 수 없는 레이블 kubernetes.io/metadata.name을
설정하며, 이 레이블 값은 네임스페이스 이름이다.
네트워크폴리시는 어떤 오브젝트 필드로 네임스페이스 이름을 직접 대상으로 삼을 수 없지만, 표준화된 레이블을 사용하여 특정 네임스페이스를 대상으로 지정할 수 있다.
새 네트워크폴리시 오브젝트가 생성되면, 네트워크 플러그인이 새 오브젝트를 처리하는 데 시간이 걸릴 수 있다. 네트워크폴리시의 영향을 받는 파드가 네트워크 플러그인이 네트워크폴리시 처리를 완료하기 전에 생성되면, 그 파드는 보호되지 않은 상태로 시작될 수 있으며, 격리 규칙은 네트워크폴리시 처리가 완료되면 적용된다.
네트워크 플러그인이 네트워크폴리시를 처리하고 나면,
주어진 네트워크폴리시의 영향을 받는 새로 생성된 모든 파드는 시작되기 전에 격리된다. 네트워크폴리시 구현은 필터링이 파드 라이프사이클 전반에서 효과적으로 동작하도록 보장해야 하며, 해당 파드의 어떤 컨테이너든 시작되는 바로 첫 순간부터도 마찬가지이다. 파드 수준에서 적용되므로, 네트워크폴리시는 초기화 컨테이너, 사이드카(sidecar) 컨테이너, 일반 컨테이너에 모두 동일하게 적용된다.
허용 규칙은 결국 격리 규칙 이후에 적용된다(또는 동시에 적용될 수도 있다). 최악의 경우에는 새로 생성된 파드가 처음 시작될 때 네트워크 연결을 전혀 갖지 못할 수 있다. 이는 격리 규칙은 이미 적용되었지만 허용 규칙은 아직 적용되지 않았기 때문이다.
생성된 모든 네트워크폴리시는 결국 네트워크 플러그인이 처리하지만, 그 일이 정확히 언제 일어나는지 쿠버네티스 API로 알 방법은 없다.
따라서 파드는 예상과 다른 네트워크 연결 상태로 시작되는 상황에도 견딜 수 있어야 한다. 파드가 시작되기 전에 특정 목적지에 도달할 수 있는지 확인해야 한다면, 초기화 컨테이너를 사용하여 kubelet이 앱 컨테이너를 시작하기 전에 해당 목적지에 도달할 수 있을 때까지 기다릴 수 있다.
모든 네트워크폴리시는 결국 선택된 모든 파드에 적용된다. 네트워크 플러그인이 네트워크폴리시를 분산 방식으로 구현할 수 있기 때문에, 파드가 처음 생성될 때 또는 파드나 정책이 변경될 때 파드가 네트워크 정책을 약간 일관되지 않게 볼 수 있다. 예를 들어, 새로 생성된 파드가 노드 1의 파드 A와 노드 2의 파드 B 모두에 도달해야 한다면, 파드 A에는 즉시 도달할 수 있지만, 파드 B에는 몇 초가 지난 뒤에야 도달할 수 있음을 발견할 수 있다.
hostNetwork 파드hostNetwork 파드에 대한 네트워크폴리시 동작은 정의되어 있지 않지만, 두 가지 가능성으로 제한되어야 한다.
hostNetwork 파드 트래픽을 다른 모든 트래픽과 구별할 수 있다.
(동일한 노드의 서로 다른 hostNetwork 파드에서 오는 트래픽까지 구별할 수 있는 경우를
포함한다.) 그리고 네트워크 플러그인은 파드 네트워크를 사용하는 파드에 적용하는 것처럼
hostNetwork 파드에도 네트워크폴리시를 적용한다.hostNetwork 파드 트래픽을 제대로 구별할 수 없어서,
podSelector와 namespaceSelector를 매칭할 때 hostNetwork 파드를 무시한다.
hostNetwork 파드로 가거나 그 파드에서 오는 트래픽은 노드 IP로 오가는 다른 모든 트래픽과 동일하게 취급된다.
(이것이 가장 일반적인 구현 방식이다.)이는 다음 경우에 적용된다.
hostNetwork 파드가 spec.podSelector에 의해 선택되는 경우.
...
spec:
podSelector:
matchLabels:
role: client
...
hostNetwork 파드가 ingress 또는 egress 규칙의 podSelector나 namespaceSelector에 의해 선택되는 경우.
...
ingress:
- from:
- podSelector:
matchLabels:
role: client
...
동시에, hostNetwork 파드는 자신이 위치한 노드와 같은 IP 주소를 가지므로,
그 연결은 노드 연결로 취급된다. 예를 들어 ipBlock 규칙을 사용하여
hostNetwork 파드에서 오는 트래픽을 허용할 수 있다.
쿠버네티스 1.37 기준으로 다음 기능은 네트워크폴리시 API에 존재하지 않지만, 운영 체제 컴포넌트(SELinux, OpenVSwitch, IPTables 등) 또는 7계층 기술(인그레스 컨트롤러, 서비스 메시 구현)이나 어드미션 컨트롤러를 사용하여 우회 방법을 구현할 수도 있다. 쿠버네티스의 네트워크 보안이 처음이라면, 다음 사용자 스토리는 네트워크폴리시 API를 사용해 (아직) 구현할 수 없다는 점에 유의할 만하다.
기존 연결에 적용되는 네트워크폴리시 집합이 변경되는 경우, 이는 네트워크폴리시 변경 때문일 수도 있고, 정책이 선택한 네임스페이스/파드의 관련 레이블 (대상과 피어 모두)이 기존 연결 도중 변경되었기 때문일 수도 있다. 이런 경우 그 변경이 해당 기존 연결에 적용될지는 구현에 따라 달라진다. 예를 들어 이전에 허용되던 연결을 거부하게 되는 정책이 생성되면, 해당 네트워크 플러그인 구현은 그 새 정책이 기존 연결을 닫을지 여부를 정의할 책임이 있다. 기존 연결에 영향을 줄 수 있는 방식으로 정책, 파드 또는 네임스페이스를 수정하지 않는 것이 좋다.