반응형

웹서비스를 인터넷에 노출시키기 위해서는 Cloud Provider 가 제공하는 Load Balancer 나 On-Prem 에서 Load Balancer 나 Ingress Controller 를 사용해야 합니다. Ingress Controller 를 사용할 때 원래의 Client IP 를 확인하는 방법은 다음과 같습니다.

 

여기서 사용하는 Ingress Controller 와 Pod 의 Web Server 는 Nginx 를 사용했습니다.

1. [ 인터넷 ] ---> [ Ingress Controller ] ---> [ Pod (Web Server) ]

가장 기본적인 방법으로 추가 세팅없이도 바로 original client ip 를 알 수 있습니다.

 

Ingress Controller 를 설치하기 위해 node label 을 세팅합니다.

$ kubectl label --overwrite node k1-node01 node-role.kubernetes.io/ingress=true

그리고 아래의 Ingress Controller yaml 로 설치합니다.

$ vi nginx-controller.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
data:
  enable-underscores-in-headers: "true"

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      hostNetwork: true
      nodeSelector:
        node-role.kubernetes.io/ingress: "true"
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10

Ingress Controller 가 설치 되었으면 echoserver 로 Pod 에서 어떻게 Client IP 를 받는지 확인할 수 있습니다.

echoserver 소스 :  https://kubernetes.io/docs/tutorials/services/source-ip/

 

Using Source IP

 

kubernetes.io

$ kubectl create ns echoserver

$ vi echoserver.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: source-ip-app
  namespace: echoserver
  labels:
    run: source-ip-app
spec:
  replicas: 1
  selector:
    matchLabels:
      run: source-ip-app
  template:
    metadata:
      labels:
        run: source-ip-app
    spec:
      containers:
      - image: k8s.gcr.io/echoserver:1.4
        name: source-ip-app
$ vi echoserver-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: clusterip
  namespace: echoserver
  labels:
    run: source-ip-app
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    run: source-ip-app
  type: ClusterIP

echoserver 는 nginx 서버이며 head 의 값을 출력하는 로직을 가지고 있습니다.

busybox 로 echoserver 출력 내용을 확인해 보겠습니다. 그러기 위해서 먼저, echoserver 의 service ip 를 확인한 후 curl 로 http request 를 호출해 봅니다.

$ kubectl get svc -n echoserver 
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
clusterip   ClusterIP   10.233.35.118   <none>        80/TCP    66s
# kubectl run busybox -it --image=busybox --restart=Never --rm -n echoserver

If you don't see a command prompt, try pressing enter.
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
3: gre0@NONE: <NOARP> mtu 1476 qdisc noop qlen 1
    link/gre 0.0.0.0 brd 0.0.0.0
4: gretap0@NONE: <BROADCAST,MULTICAST> mtu 1462 qdisc noop qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
6: eth0@if404: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 5e:8e:b0:19:be:56 brd ff:ff:ff:ff:ff:ff
    inet 10.233.125.19/32 scope global eth0
       valid_lft forever preferred_lft forever

/ # wget -qO - 10.233.35.118
CLIENT VALUES:
client_address=10.233.125.19
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://10.233.35.118:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
connection=close
host=10.233.35.118
user-agent=Wget
BODY:
-no body in request-

pod 에서 service 를 통해 web server 를 호출하면 client_address 에 client ip 가 제대로 출력됩니다. 그러나, Ingress Controller 호출하는 경우에는 다음과 같습니다.

 

Ingress Controller 는 k1-node01 (192.168.30.12) 에 떠있고, k1-node05 (192.168.30.20) 에서 echoserver 를 호출해 보겠습니다.

echoserver 를 위한 Ingress 를 생성합니다.

$ vi echoserver-ingress.yaml 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: echoserver
  namespace: echoserver
spec:
  rules:
  - host: echoserver.ip
    http:
      paths:
      - backend:
          serviceName: clusterip
          servicePort: 80
        path: /

k1-node05 서버에서 echoserver.ip 도메인을 hosts 파일에 등록하고, echoserver 를 호출합니다.

$ cat /etc/hosts
192.168.30.12    k1-node01    echoserver.ip
192.168.30.20    k1-node05



$ curl -X GET echoserver.ip -H 'Host: echoserver.ip'
CLIENT VALUES:
client_address=192.168.30.12
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://echoserver.ip:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=echoserver.ip
user-agent=curl/7.47.0
x-forwarded-for=192.168.30.20
x-forwarded-host=echoserver.ip
x-forwarded-port=80
x-forwarded-proto=http
x-original-uri=/
x-real-ip=192.168.30.20
x-request-id=15b32d8bb2a8945f0dfebf3fae29c736
x-scheme=http
BODY:
-no body in request-

client_address 에는 Ingress Controller 가 떠있는 서버의 ip 가 들어오고, header 의 x-forwared-for 값에 original client ip 가 출력되는 것을 알 수 있습니다.

 

실제 Ingress Controller 의 echoserver 에 대한 config 값은 다음과 같습니다.

proxy_set_header X-Request-ID           $req_id;
proxy_set_header X-Real-IP              $the_real_ip;
             
proxy_set_header X-Forwarded-For        $the_real_ip;
             
proxy_set_header X-Forwarded-Host       $best_http_host;
proxy_set_header X-Forwarded-Port       $pass_port;
proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
             
proxy_set_header X-Original-URI         $request_uri;
             
proxy_set_header X-Scheme               $pass_access_scheme;
             
# Pass the original X-Forwarded-For
proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

Pod 의 nginx 에서 client ip 에 original client ip 를 출력하고 싶을 때는 pod nginx 에 다음과 같은 설정이 필요합니다. (추가 확인 필요)

set_real_ip_from  192.168.30.0/24;
real_ip_header    X-Forwarded-For;

set_real_ip_from 은 ingress controller 가 실행될 수 있는 서버의 cidr 입니다.

 

 

2. [ 인터넷 ] ---> [ Google or Azure LB ] ---> [ Pod (Web Server) ]

Cloud Provider (Google or Azure) 의 LB 를 이용하고 Service 의  LoadBalancer type 으로 웹서비스를 한다면 아래와 같이 Service 의  externalTrafficPolicy 값을 local 로 지정하면 됩니다. local 로 지정하면 LB 에서 바로 Web Server Pod 가 떠 있는 서버로만 접근되기 때문에 다른 서버를 통해서 연결될 때 client ip 가 해당 서버의 ip 로 들어가는 것을 방지해 줍니다.

---
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  ports:
  - port: 8765
    targetPort: 9376
  selector:
    app: example
  type: LoadBalancer
  externalTrafficPolicy: Local

Azure 부분은 한국 MS 의 박인혜 차장께서 알려주셨습니다.

 

3. [ 인터넷 ] ---> [ AWS ELB (classic lb) ] ---> [ Pod (Web Server) ]

Classic LB 에 proxy protocol 을 enable 하고, Service 의 annotations 에 다음과 같이 설정합니다.

# Enable PROXY protocol
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'https'
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-myarn-long-entry-obfuscated-here

 

4. [ 인터넷 ] ---> [ AWS NLB ] ---> [ Ingress Controller ] ---> [ Pod (Web Server) ]

Ingress Controller 의 config 에 다음과 같이 설정합니다.

use-proxy-protocol: "true"
real-ip-header: "proxy_protocol"

 

AWS NLB 의 각 타겟 그룹에 Proxy Protocol V2 를 설정한 후,  Ingress Controller 의 Service annotations 에 다음과 같이 설정합니다.

# by default the type is elb (classic load balancer).
service.beta.kubernetes.io/aws-load-balancer-type: nlb
# Enable PROXY protocol
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'tcp'

 

※ AWS 와 Google 의 경우에는 테스트 해보지 못했습니다. 

반응형
Posted by seungkyua@gmail.com
,