스터디

[KANS 3기] 6주차 Ingress

엔지니어-여리 2024. 10. 12. 17:23
반응형

 

실습환경

 

구성하기

실습환경은 지난 번과 동일하게 cloudformation으로 구성합니다.

 

먼저, 실습 template를 입력합니다.

https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-6w.yaml

 

 

그 뒤에 파라미터를 입력해줍니다.

stack name은 적절히 mylab으로 입력했습니다.

ec2 key-pair 이름은 이전에 만들어 둔 키페어를 재활용했습니다.

t3.medium이 기본 값이지만 필요시 더 큰 사이즈의 인스턴스로 변경하시면 됩니다.

 

 

모든 입력을 완료 한 후 submit을 클릭하여 스택을 배포합니다.

 

잠시 기다리면 모든 배포가 완료된 것을 확인할 수 있습니다.

 

4대의 인스턴스가 생성되어 있는 걸 알 수 있습니다.

 

확인하기

 

cloudformation으로 구성된 실습환경을 확인하겠습니다.

먼저, EC2 서비스에서 k3s-s 인스턴스의 public IP를 확인합니다.

 

이후, terminal에서 ssh 명령으로 k3s-s 인스턴스에 접속합니다.

 

노드 정보

 kubectl get nodes -o wide

 

 

파드 정보

kubectl get pod -o wide -A

 

config 정보

 

# -v 는 verbose 옵션입니다. kubectl 프로그램의 로그 레벨을 더 높인다는 의미입니다.
kubectl get pod -v=6

 

 

config 파일의 위치입니다.

/etc/rancher/k3s/k3s.yaml

 

네트워크 확인

# 인스턴스에 등록된 ip 리스트
ip -c addr

# 인스턴스에 등록된 라우팅 정보
ip -c route

#flannel cni의 서브넷 정보
cat /run/flannel/subnet.env

# 쿠버네티스에 등록된 노드들이 가지고 있는 pod CIDR 정보
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo

# 노드의 annotation 정보. 여기서는 각 노드의 flannel cni 구성 정보 확인
kubectl describe node | grep -A3 Annotations

# 이더넷 브릿지 정보 확인
brctl show

 

 

 

서비스, endpoint 정보

kubectl get svc,ep -A

kubectl 명령어를 활용하여 서비스, 엔드포인트 정보를 확인합니다. 

 

iptables 정보 확인

iptables -t filter -S | wc -l
iptables -t nat -S | wc -l
iptables -t mangle -S | wc -l

 

각 테이블 (filter, nat, mangle)의 iptables 규칙 확인이 가능합니다.

 

위와 같이 실습환경을 준비하였고, 구성을 확인하였습니다.

 

Ingress

소개

오늘 살펴볼 주제는 Ingress 입니다.  인그레스는 클러스터 외부의 요청을 클러스터 내부로 전달해주는 역할을 합니다.

아니 서비스에서 이미 클러스터 외부에 있는 요청을 내부로 전달하지 않았나요 ?

 

서비스의 노드포트나 로드밸런서 타입도 물론 클러스터 외부의 요청을 내부로 전달할 수 있지만 인그레스는 조금 다르게 동작합니다. 

 

위 도표와 같이 Ingress는 통신 암호화와 L7 계층에서 동작을 지원합니다. 이를 통해 외부로 노출된 인그레스 리소스로부터 적절한 서비스 - 파드로 HTTP, HTTPS 요청이 전달될 수 있습니다. 이를 간단한 구조로 표현하면 다음과 같습니다.

쿠버네티스 인그레스 문서

 

물론, 인그레스를 어떻게 구성하냐에 따라 인그레스로부터 파드로 직접통신될 수 있습니다. 

 

이러한 인그레스를 사용하기 위해서는 인그레스 컨트롤러를 구성해야합니다. 인그레스 컨트롤러는  실제 인그레스의 동작을 구현하는 구현체라고 생각하면 됩니다. 인그레스 컨트롤러는 https://kubernetes.io/ko/docs/concepts/services-networking/ingress-controllers/ 에서 더 찾아볼 수 있습니다. 

결국, 쿠버네티스에서는 인그레스 API만을 정의하고 실제 구현은 Add-on 에 맡기는 구조입니다. ingress 중에서 ingress-nginx에 대해 살펴보겠습니다. nginx-ingress와는 다릅니다. nginx-ingress는 F5에서 관리하는 인그레스 컨트롤러이고 ingress-nginx는 쿠버네티스에서 관리하는 인그레스 컨트롤러입니다. (헷갈리지 말것)

알아두어야할 내용

인그레스는 현재 새로운 기능이 제공되지 않습니다. 인그레스를 대신할 gateway API라는 다른 유형의 리소스가 등장하였습니다. 

ingress-nginx 실습

cat <<EOT> ingress-nginx-values.yaml
controller:
  service:
    type: NodePort
    nodePorts:
      http: 30080
      https: 30443
  nodeSelector:
    kubernetes.io/hostname: "k3s-s"
  metrics:
    enabled: true
  serviceMonitor:
      enabled: true
EOT

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

kubectl create ns ingress
helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx-values.yaml --namespace ingress --version 4.11.2

# externalTrafficPolicy 설정
kubectl patch svc -n ingress ingress-nginx-controller -p '{"spec":{"externalTrafficPolicy": "Local"}}'

위 명령어를 실행해보겠습니다.

1. ingress-nginx controller 매니페스트를 정의합니다.

2. ingress-nginx 헬름을 추가하고 앞서 정의한 컨트롤러 메니페스트를 적용합니다.

설정 확인

리소스가 정상적으로 적용되었는지 확인합니다. configmap 에는 ingress 리소스가 참조하는 label, annotation이 포함되어 있는 것을 확인할 수 있습니다.

 

 kubectl get configmap ingress-nginx-controller -n ingress -o yaml

 

 

kc describe clusterroles ingress-nginx

컨트롤러의 권한을 확인합니다.

 

인그레스 컨트롤러 파드에서 ingress 컨트롤러의 정보를 확인할 수 있습니다.

POD_NAMESPACE=ingress
POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o name)
kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version

여기서 웃긴 점은 앞에서 ingress-nginx, nginx-ingress차이를 말했었죠? 우리가 구성한 인그레스 컨트롤러는 ingress-nginx인데 인그레스 컨트롤러 파드에서 컨트롤러 정보를 확인하는 명령어는 nginx-ingress-controller 입니다. 뭔가 여기저기서 마구잡이로 사용되었을 수 있다는 느낌이 듭니다.

 

Ingress 실습

 

이제 본격적으로 인그레스 실습을 해보겠습니다. 실습할 구성은 다음과 같습니다.

 

실습 목표

- 컨트롤플레인 노드에 인그레스 컨트롤러 생성, NodePort 외부 노출

- 인그레스 정책 설정 (Host/Path Routing)

 

실습내용을 구성해보겠습니다.

아래는 실행되는 매니페스트 상세내용입니다.

매니페스트

더보기

svc1-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1-websrv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: websrv
  template:
    metadata:
      labels:
        app: websrv
    spec:
      containers:
      - name: pod-web
        image: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: svc1-web
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 80
  selector:
    app: websrv
  type: ClusterIP

svc2-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy2-guestsrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: guestsrv
  template:
    metadata:
      labels:
        app: guestsrv
    spec:
      containers:
      - name: pod-guest
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc2-guest
spec:
  ports:
    - name: guest-port
      port: 9002
      targetPort: 8080
  selector:
    app: guestsrv
  type: NodePort

svc3-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy3-adminsrv
spec:
  replicas: 3
  selector:
    matchLabels:
      app: adminsrv
  template:
    metadata:
      labels:
        app: adminsrv
    spec:
      containers:
      - name: pod-admin
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc3-admin
spec:
  ports:
    - name: admin-port
      port: 9003
      targetPort: 8080
  selector:
    app: adminsrv

 

ingress1.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-1
  annotations:
    #nginx.ingress.kubernetes.io/upstream-hash-by: "true"
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc1-web
            port:
              number: 80
      - path: /guest
        pathType: Prefix
        backend:
          service:
            name: svc2-guest
            port:
              number: 8080
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080

 

ingress2.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-2
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
  - host: "*.kans.com"
    http:
      paths:
      - path: /echo
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080

 

canary-svc1-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dp-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: svc-v1
  template:
    metadata:
      labels:
        app: svc-v1
    spec:
      containers:
      - name: pod-v1
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
      terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v1
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 8080
  selector:
    app: svc-v1

 

canary-svc2-pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dp-v2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: svc-v2
  template:
    metadata:
      labels:
        app: svc-v2
    spec:
      containers:
      - name: pod-v2
        image: k8s.gcr.io/echoserver:1.6
        ports:
        - containerPort: 8080
      terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v2
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 8080
  selector:
    app: svc-v2

canary-ingress1.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-canary-v1
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-v1
            port:
              number: 8080

 

canary-ingress2.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-canary-v2
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
  - host: kans.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-v2
            port:
              number: 8080

 

 

 

실습 1. 인그레스 적용 및 리소스 확인

kubectl taint nodes k3s-s role=controlplane:NoSchedule
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc1-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc2-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/svc3-pod.yaml
kubectl apply -f svc1-pod.yaml,svc2-pod.yaml,svc3-pod.yaml

위 코드블럭을 실행시키면 아래 동영상과 같이 pod,ep, svc가 구성됩니다.

 

위에 설정된 파드, 엔드포인트, 서비스에 인그레스 설정을 추가해보겠습니다. 위에 정의한 ingress1.yaml을 적용해줍니다.

아래 영상과 같이 ingress - svc - pod 구성이 적용됩니다.

 

 

 

인그레스 컨트롤러에 기록된 Rule은 실제로 인그레스 컨트롤러 파드의 nginx.conf에 적용됩니다.

kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf | grep 'location /' -A5 | more

인그레스 컨트롤러를 통해서 접속해보겠습니다.

 

컨트롤러의 인입 포트(30080)와 Public IP(13.124.30.188)를 확인한 다음 Rule에 맞게 url을 입력하여 접속해보겠습니다.

 

 

http://13.124.30.188:30080/

http://13.124.30.188:30080/guest

http://13.124.30.188:30080/admin

 

접속이 잘 되는 것을 확인할 수 있습니다.

 

실습 2. 패킷 캡쳐 및 분석

패킷을 캡쳐하여 통신이 어떤식으로 이루어지는지 알아보겠습니다.

 

아래 명령어를 활용하여 패킷을 저장 해줍니다.

tcpdump -i $(ifconfig  | grep veth | tail -1 | awk '{print $1}' | cut -d ':' -f 1) tcp port 8080 -w /home/ubuntu/ingress-nginx-admin.pcap && chown ubuntu /home/ubuntu/ingress-*

 

http://13.124.30.188:30080/admin 주소에 1회 접속 후 패킷캡쳐를 종료합니다.

 

scp -i ~/.ssh/yeoli.pem ubuntu@13.124.30.188:/home/ubuntu/ingress-nginx-admin.pcap .

 

패킷을 다운로드 받은 다음 Wireshark로 패킷 덤프를 열어보겠습니다.

이 패킷 덤프에서는 172.16.0.4 와 172.16.3.5 사이에 통신이 있는 것으로 보입니다.

이를 cluster에서 리소스를 조회해보면 172.16.0.4는 ingress controller의 ip, 172.16.3.5는 admin pod의 ip로 확인됩니다.

 

실제로 인그레스 컨트롤러

HTTP 요청에서는 요청자 IP(저의 public ip)와 클러스터의 노드포트 정보가 포함되어 있습니다.

 

 

실습 3. 호스트 기반 라우팅

ingress2.yaml 매니페스트를 적용해보겠습니다.

적용시 아래와 같이 ingress-2 가 생성됩니다.

호스트는 kans.com, *.kans.com이 아닌 yeoli.com, *.yeoli.com 로 설정하였습니다.

 

이후 내 PC에서 host 파일을 수정해야합니다. root 계정에서 아래 명령어를 사용하여 hosts 파일을 수정해줍니다.

K3S_PUBLIC_IP='13.124.30.188' # 구성된 클러스터의 public ip를 기입해주세요.
echo $K3S_PUBLIC_IP yeoli.com >> /etc/hosts

 

잘 접속되는 것을 확인할 수 있습니다.

실습 4. 카나리 업그레이드

 

카나리 업그레이드 대상이 될 파드를 배포해줍니다.

kubectl apply -f canary-svc1-pod.yaml,canary-svc2-pod.yaml

 

 

마찬가지로 인그레스도 배포해줍니다.

kubectl apply -f canary-ingress1.yaml,canary-ingress2.yaml

 

여기서 중요한 부분은 

canary-ingress2.yaml의 annotation입니다.

canary: true
canary-weight: 10 

즉 카나리 설정을 활성화하고 카나리 비율을 10퍼센트로 한다는 설정을 포함합니다. 

이 weight은 ingress-canary-v2로 들어오는 요청의 비율을 뜻합니다.

kubectl annotate --overwrite ingress ingress-canary-v2 nginx.ingress.kubernetes.io/canary-weight=50

 

ingress-canary-v2 로 들어오는 요청을 조절하면 아래 동영상과 같이 실시간으로 버전에 따른 요청량을 조절할 수 있습니다.

개발 사이클에서 사용자 경험을 더욱 높일 수 있는 방법으로 활용될 수 있습니다.

 

 

정리

단순히 서비스를 활용하면 쿠버네티스 클러스터 내부에 있는 파드를외부에서 요청할 수 있습니다. 다만 클러스터 내에서 여러 서비스를 다양하게 제어 하기에는 역부족입니다. 이를 해결해주기 위해 인그레스를 도입할 수 있습니다. 인그레스를 통해 더 다양한 요구사항을 처리할 수 있습니다.

 

인그레스 도입시 활용가능한 기능은 다음과 같습니다.

 

- 호스트라우팅

- 카나리 배포전략

- 애플리케이션 계층 구성

 

하지만, 인그레스는 앞으로 추가적인 개발이 없을 예정입니다. 인그레스를 대체할 리소스는 Gateway API 입니다. Ingress를 잘 알고 Gateway API로 잘 migration하여 즐거운 쿠버네티스 삶이 되시길 바랍니다.

 

감사합니다.

 

 

반응형