Istio Sidecar 설정을 통한 Pod 의 egress 제어

Istio Sidecar 설정을 통한 Pod 의 egress 제어
💡
Istio 의 Sidecar 설정을 통해 각 워크로드에 붙어있는 사이드카 프록시의 구성 설정을 변경 할 수 있습니다. 이 포스트에서는 이 설정을 통해 특정 서비스가, 지정된 서비스만 호출 할 수 있도록 제어하는 방법을 소개합니다.

Network Policy

기본적으로 쿠버네티스에서 파드 - 파드 , 파드 - 서비스 등의 모든 통신은 기본적으로 허용이 되어있다.

이 때 Kubernetes 에서 네이티브하게 지원하는 Network Policy 로 아래와 같은 네트워크 정책을 잡아 파드-파드 등의 호출을 불가능하도록 막을 수 있다.

  1. 다른 파드 의 요청을 허용할지 여부 (파드 자체의 트래픽, 즉 파드 안의 컨테이너간의 요청을 제어 할 수는 없음)
  2. 다른 네임스페이스에 존재하는 워크로드로부터의 요청을 허용할지 여부
  3. IP 블록 기반의 허용 여부 (이 설정과 관계없이 파드가 실행중인 노드와의 트래픽은 항상 허용됨)

아래는 Network Policy 오브젝트의 예시이다.

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

ingress 와 egress 로 들어가고 나가는 트래픽에 대해서 파드를 선택하거나, 네임스페이스를 선택하거나, ip 대역을 지정해서 허용 여부를 결정 할 수 있다.

다만 Network Policy 로는 다음과 같은 것을 할 수 없다.

  1. 내부 클러스터 트래픽이 특정 게이트웨이를 통과하도록 강제 (서비스 메시나 기타 프록시를 사용하는것을 권장)
  2. TLS 와 관련된 모든것 (서비스 메시나 Ingress Controller 사용 권장)
  3. 노드 별 정책
  4. 서비스 네임(도메인 네임)을 타게팅한 정책
  5. 모든 네임스페이스 또는 파드에 반영되는 "기본" 정책

기본적으로 쿠버네티스 환경에서 내부 서비스간 호출은 "서비스" 오브젝트의 도메인을 호출해서 사용하는것이 보편적이다. (e.g example-service.example.svc.cluster.local)

하지만 위에서 알 수 있듯이 서비스 네임 기반의 네트워크 트래픽 정책을 Network Policy 로는 구성 할 수 없다 이 때 Istio 를 쓰고있다면 이런 구성을 할 수 있다

Istio Sidecar

예를들어서 A 애플리케이션은 오직 B 데이터베이스와, C 서비스(애플리케이션)과만 통신을 하면 되고, 그 외에 것들과는 통신을 할 필요가 없다고 했을 때. Istio Sidecar 를 별도로 설정하지 않은 경우 B 데이터베이스, C서비스를 포함해서 그 외 D,E,F,G,…. 등의 같은 쿠버네티스 클러스터 안에 있는 다른 서비스들을 호출 할 수 있는 상태가 된다.

따라서 Sidecar 설정을 통해서 Egress 트래픽을 제한함으로서 특정 서비스가 허용된 서비스만 호출 할 수 있도록 강제 할 수 있다.

어떻게 설정 할 수 있나요?

일단 Istio 의 MeshConfig 부터 손을 봐야하는데.

Global Mesh Options

Istio 의 서비스 메시 글로벌 옵션인 outboundTrafficPolicy 가 기본적으로는 ALLOW_ANY로 되어있다. 이것은 대상 포트에 대한 서비스 또는 서비스 엔트리가 없는 경우 무조건적으로 아웃바운드 트래픽을 허용한다는 의미이다.

쉽게 생각해서 내부 서비스를 포함해서 외부 엔드포인트(e.g www.google.com)에 대해서 모두 다 아웃바운드 트래픽을 허용한다는 의미이다.

REGISTRY_ONLY 옵션의 경우 아웃바운트 트래픽으로 서비스 엔트리로 관리하고있거나, 서비스 레지스트리에 등록된 서비스만 아웃바운드 트래픽을 허용한다는 의미이다.

여기서 Sidecar 옵션을 활용하기 위해서는 outboundTrafficPolicyREGISTRY_ONLY 로 수정하고 진행해야 한다.

💡
Service Registry : Istio 가 인지하고있는 트래픽을 라우팅 할 수 있는 서비스 오브젝트(혹은 가상 서비스), 서비스 엔트리등을 의미한다.
💡
Service Entry : 서비스 엔트리는 Istio 내부 서비스 레지스트리에 Mesh 외부 서비스를 추가해서 서비스 메시 안에서 서비스 엔트리로 수동으로 추가한 서비스에 대해서 Istio 가 트래픽을 라우팅 할 수 있도록 하는 개념이라고 생각 할 수 있다. 예를들어 outboundTrafficPolicy가 ALLOW_ANY 인 경우 Istio (혹은 쿠버네티스)가 관리하지 않는 서비스 도메인인 경우 Istio 서비스 레지스트리에는 없지만, 어쩄든 outbound traffic 은 허용되는 상태로 트래픽이 넘어 갈 수 있다. 반면 REGISTRY_ONLY 인 경우 서비스 엔트리로 수동으로 추가하지 않는 경우 트래픽이 라우팅 되지 않는다. (Istio 혹은 쿠버네티스 내부에서 관리하는 서비스 도메인, 서비스 등이 아닌경우)
meshConfig:
  outboundTrafficPolicy:
    mode: REGISTRY_ONLY

위와 같이 Istio 의 meshConfig 의 outboundTrafficPolicy mode 를 수정한다. (디폴트는 ALLOW_ANY)

apiVersion: v1
kind: Namespace
metadata:
  name: red
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: blue
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: green
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-red-1
  namespace: red
  labels:
    name: nginx-red-1
    app: nginx-red-1
spec:
  containers:
  - name: nginx
    image: nginx:latest
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-blue-1
  namespace: blue
  labels:
    name: nginx-blue-1
    app: nginx-blue-1
spec:
  containers:
  - name: nginx
    image: nginx:latest
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-green-1
  namespace: green
  labels:
    name: nginx-green-1
    app: nginx-green-1
spec:
  containers:
  - name: nginx
    image: nginx:latest

테스트를위해 red, green, blue 라는 세 네임스페이스에, nginx 파드를 각각 띄우도록 위 yaml 을 반영한다. 이 때 sidecar 는 모두 inject 되도록 Namespace 레벨에서 istio-injection: enabled 레이블이 붙어있다.

apiVersion: v1
kind: Service
metadata:
  name: nginx-red-svc
  namespace: red
spec:
  selector:
    name: nginx-red-1
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-blue-svc
  namespace: blue
spec:
  selector:
    app: nginx-blue-1
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-green-svc
  namespace: green
spec:
  selector:
    app: nginx-green-1
  ports:
  - port: 80
    targetPort: 80

이후 각 파드에 대한 서비스 오브젝트를 생성한다.

이 상태에서 red 네임스페이스의 파드에서 green 네임스페이스의 파드로, red 네임스페이스 파드에서 blue 네임스페이스의 파드로 HTTP 요청을 아래와 같이 해본다.

$ kubectl exec -it -n red nginx-red-1 -- curl -XGET -v nginx-green-svc.green.svc.cluster.local

$ kubectl exec -it -n red nginx-red-1 -- curl -XGET -v nginx-blue-svc.blue.svc.cluster.local

Istio Sidecar 리소스를 생성해서 egress 를 강제하지 않았기 때문에 요청에 대한 응답이 잘 들어오게 된다.

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: red-sidecar
  namespace: red
spec:
  egress:
  - hosts:
    - "istio-system/*"

이 때 red 네임스페이스에 대해서 egress 항목에 hostsistio-system/* 로 지정하여 Sidecar 리소스를 생성해보자.

이것은 istio-system 을 제외한 나머지 네임스페이스에 존재하는 서비스 로 트래픽을 outbound 하지 않도록 하겠다는 설정을 의미한다.

💡
이때 hosts 내의 항목은 namespace/dnsName 포멧으로 작성한다. 위 케이스는 istio-system 네임스페이스의 모든 호스트에 대한 egress 를 허용한다는 의미이다.

이후

$ kubectl exec -it -n red nginx-red-1 -- curl -XGET -v nginx-green-svc.green.svc.cluster.local

$ kubectl exec -it -n red nginx-red-1 -- curl -XGET -v nginx-blue-svc.blue.svc.cluster.local

을 통해 red 네임스페이스에서 green , blue 네임스페이스의 서비스를 호출 해보면 502 BadGateway 가 뜨면서 트래픽이 넘어가지 않는 것을 확인 할 수 있다.

502 Bad Gateway 응답
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: red-sidecar
  namespace: red
spec:
  egress:
  - hosts:
    - "istio-system/*"
    - "green/*"
    - "blue/*"

위와 같이 사이드카 오브젝트를 수정하고 반영한뒤 요청하면, 이 때는 응답이 잘 들어온다.

REGISTRY_ONLY 로 meshConfig 내 설정을 변경했더니 외부 도메인에 대한 트래픽 처리가 안되요!

위와 같이 Sidecar 리소스를 설정하고, outboundTrafficPolicy.mode 가 REGISTRY_ONLY 인 경우 egress 에 등록된 호스트(서비스) 외에는 트래픽이 넘어가지 않게 된다.

만약 red nginx 앱에서 www.google.com 을 호출하고 싶다면 아래와 같이 설정해야한다.

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: red-sidecar
  namespace: red
spec:
  egress:
  - hosts:
    - "istio-system/*"
    - "green/*" # green 네임스페이스로 넘어가는 outbound 트래픽 허용
    - "blue/*" # blue 네임스페이스로 넘어가는 outbound 트래픽 허용
    - "red/*" # 자기 자신의 네임스페이스에 대한 모든 호스트 허용 -> service entry 추가 항목 반영 (ServiceEntry 를 red 네임스페이스에 생성했기 때문에)
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: svc-entry
  namespace: red
spec:
  hosts:
  - www.google.com
  ports:
  - number: 443
    name: https
    protocol: TLS
  - number: 80
    name: http
    protocol: HTTP
  location: MESH_EXTERNAL
  resolution: DNS

위와 같이 ServiceEntry 오브젝트를 생성하고, Sidecar 오브젝트 또한 자기 자신의 네임스페이스를 지정하면 www.google.com 에 대한 egress 트래픽도 허용되게 된다.

💡
위 케이스에서 ServiceEntry 오브젝트에 대한 네임스페이스가 red 이기 때문에 Sidecar 에서도 red/* 을 지정했다. 만약 해당 오브젝트가 다른 네임스페이스에 있다면 다른 네임스페이스를 지정하면 된다.
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: google
  namespace: entries
spec:
  hosts:
  - www.google.com
  ports:
  - number: 443
    name: https
    protocol: TLS
  - number: 80
    name: http
    protocol: HTTP
  location: MESH_EXTERNAL
  resolution: DNS

위와 같이 entries 라는 네임스페이스에 ServiceEntry 를 생성했다면.

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
...
spec:
  egress:
  - hosts:
    - "istio-system/*"
    - "entries/www.google.com"

과 같은 형태로 네임스페이스/FQDN 으로 지정해서 www.google.com 에 대한 egress 트래픽을 허용 할 수 있다.