새소식

Kubernetes

[Study 6주차] 쿠버네티스 Ingress

  • -

CoudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 6주차 스터디 내용 정리

 

이번 주차에서는 쿠버네티스의 Ingress 동작 방식에 대해서 공부하였습니다.

 

 

1. Ingress

 

1.1. Ingress 소개

 

출처 : https://kubernetes.io/docs/concepts/services-networking/ingress/

 

Ingress 는 클러스터 외부에서 클러스터 내부의 파드에 접근하기 위한 진입점 역할을 합니다.

 

앞서 Service 또한 진입점 역할을 한다고 배웠었는데요,
Ingress 와 Service 의 가장 큰 차이점은 Ingress 는 7계층에서 동작하고, Service 는 4계층 동작을 한다는 것입니다.

 

즉, Ingress 는 HTTP / HTTPS 통신 요청을 받아서 처리해주는 것이 가능하며

이를 이용한 경로기반이나 호스트 기반 라우팅 등의 고급 라우팅 기능을 사용할 수 있습니다.

 

출처 : 가시다님 책 원고



 

또한 Ingress 를 활용하면 카나리 배포가 가능하여 유연한 업데이트 전략을 사용할 수 있습니다.

 

출처 : 가시다님 책 원고

 

마지막으로 SSL/TLS Termination 을 지원합니다.

 

출처 : 가시다님 책 원고

 

 

정리하자면, Service 와 구별되는 Ingress 의 기능은 다음과 같습니다.

 

Ingress 특징

 

1. HTTP/HTTPS 을 활용한 경로 기반, 호스트 기반의 고급 라우팅 가능
2. 카나리 배포 지원
3. SSL/TLS 종료 지원

 

 

그렇다면 Ingress 는 어떻게 동작할까요??
Ingress 의 실제 동작은 Ingress Controller 에 의해서 처리됩니다.

 

쿠버네티스는 Ingress 의 API 명세만 정의하고 실제 구현은 다양한 Ingress Controller 구현체에 의해서 진행됩니다.
이번 실습에서는 가장 유명하고 널리 쓰이는 Ingress Nginx Controller 를 사용하여 Ingress 의 동작 원리를 공부해보겠습니다.



1.2. 쿠버네티스 실습 환경 구성

 

Ingress 의 실습 환경은 AWS EC2 를 기반으로 구성한 K3S 클러스터를 활용하였습니다. (Control Plane 1대, Worker Node 3대)

 

CloudFormation 을 통한 Kubernetes 구성

 

# 6주차 Ingress 실습 환경 구성
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-6w.yaml

# CloudFormation 배포 변수 설정 및 배포
TACK_NAME=kimalarm-stack
AWS_REGION=ap-northeast-2
KEY_NAME=kimalarmkey

aws cloudformation deploy \
  --template-file kans-6w.yaml \
  --stack-name ${STACK_NAME} \
  --parameter-overrides KeyName=${KEY_NAME} SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \
  --region ${AWS_REGION}

# Control Plane 서버 접속
ssh -i ~/kans/kimalarmkey.pem ubuntu@43.203.179.198

 



1.3. Ingress Nginx 구성

 

Ingress Nginx 는 Helm 을 사용해서 설치하겠습니다.

 

# 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

# 설치 확인
kubectl get all -n ingress

# Kubecolor 활용하여 서비스 확인
kc describe svc -n ingress ingress-nginx-controller

 

 

 

실습 편의를 위한 부가적인 설정 및 확인

 

# externalTrafficPolicy 설정 - tcpdump 확인 시, 손쉬운 확인을 위해 활성화
kubectl patch svc -n ingress ingress-nginx-controller -p '{"spec":{"externalTrafficPolicy": "Local"}}'

# 기본 nginx conf 파일 확인
kubectl exec deplo
y/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf

# Ingress Nginx 버전 정보 확인
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

 



 

2. Ingress Nginx 실습

 

출처 : kans 스터디

 

 

실습 구성도 개요

 

1. Control Plane 노드에 Ingress Controller 파드를 생성
2. Service 를 NodePort 로 외부 노출

 

 

2.1. 실습 환경 구성

 

Deployment & Service 정보

 

총 3개의 디플로이먼트와 서비스를 배포할 예정입니다.

 

 

첫번 째

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

 

두 번째

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

 

세 번째

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

 

Deployment & Service 배포

# 생성
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

# 배포 확인
kubectl get pod,svc,ep

 

 

svc1 과 svc3 은 ClusterIP 이지만, svc2 는 NodePort 인 것을 알 수 있습니다.

ClusterIP 를 활용하면 놀랍게도 Pod 의 ClusterIP 로 트래픽을 Direct Pass 해줄 수 있습니다.

 

이는 Ingress Controller 가 Service 와 연결된 Pod 의 Service Endpoint (ClusterIP) 리스트를 조회할 수 있는 권한을 가지고 있으며, 이를 주기적으로 체크해서 Ingress 구현체에게 전달해주기 때문입니다.

kubectl describe clusterrole ingress -n ingress | egrep '(Verbs|endpoints)'
kubectl describe roles ingress-nginx -n ingress | egrep '(Verbs|endpoints)'

 

 

Ingress 배포

 

# Ingress 배포 파일 생성
cat <<EOT> 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
EOT

# Ingress 배포
kubectl apply -f ingress1.yaml

# Ingress 배포 확인
kc describe ingress ingress-1

 

 

 

2.2. Ingress 를 사용한 내부 접속

 

출처 : 가시다님 책 원고

 

# Ingress 확인
kubectl get ingress

# 자신의 집 PC 에서 인그레스 접속
echo -e "Ingress1 sv1-web URL = http://$(curl -s ipinfo.io/ip):30080"
echo -e "Ingress1 sv2-guest URL = http://$(curl -s ipinfo.io/ip):30080/guest"
echo -e "Ingress1 sv3-admin URL = http://$(curl -s ipinfo.io/ip):30080/admin"

# PC 로컬에서 서버의 NodePort 로 접속
MYIP=<EC2 공인 IP>
MYIP=43.203.179.198
curl -s $MYIP:30080
curl -s $MYIP:30080/guest
curl -s $MYIP:30080/admin

 

 

X-Forwarded-For 설정이 적용되어 있기 때문에, 실제 Client IP (저의 로컬 PC가 사용하는 공인 IP)가 출력되는 것을 알 수 있습니다.

# 실습 종료 후 리소스 삭제
kubectl delete deployments,svc,ingress --all



2.3. Host 기반 라우팅

 

Ingress 에 매칭된 Host 이름을 보고 라우팅을 해주는 방법에 대해서 공부해보겠습니다.

 

# host 명을 kimalarm.com 으로 사용
cat <<EOT> ingress2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-2
spec:
  ingressClassName: nginx
  rules:
  - host: kimalarm.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
  - host: "*.kimalarm.com"
    http:
      paths:
      - path: /echo
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
EOT

# Ingress 배포
kubectl apply -f ingress2.yaml,svc3-pod.yaml

# 배포 확인
kubectl get ingress
kubectl describe ingress ingress-2 | sed -n "5, \$p"

 

 

Local PC 의 host 파일 변경

 

접속 테스트를 위해 host 파일을 변경합니다.

MYDOMAIN1=kimalarm.com
MYDOMAIN2=test.kimalarm.com
echo $MYIP $MYDOMAIN1 $MYDOMAIN2

echo "$MYIP $MYDOMAIN1" | sudo tee -a /etc/hosts
echo "$MYIP $MYDOMAIN2" | sudo tee -a /etc/hosts
cat /etc/hosts | grep $MYDOMAIN1

 

 

Service 호출

 

# Domain 1번 호출 - kimalarm.com
curl $MYDOMAIN1:30080 -v
curl $MYDOMAIN1:30080/admin
curl $MYDOMAIN1:30080/echo
curl $MYDOMAIN1:30080/echo/1

 

 

# Domain 2번 호출 - test.kimalarm.com
curl $MYDOMAIN2:30080 -v
curl $MYDOMAIN2:30080/admin
curl $MYDOMAIN2:30080/echo
curl $MYDOMAIN2:30080/echo/1

 

 

test.kimalarm.com 으로 호출하는 규칙은 /echo 경로에만 규칙이 존재하기 때문에,

/echo 경로가 아닌 다른 경로는 404 오류를 발생시키는 것을 알 수 있습니다.

 

# 실습 종료 후 리소스 삭제
kubectl delete deployments,svc,ingress --all



2.4. Canary 업데이트

 

Ingress 를 통한 카나리 업데이트 방법에 대해서 공부해보겠습니다.

 

버전 1 디플로이먼트

 

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

 

버전 2 디플로이먼트

 

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

 

생성 및 확인

 

# 배포
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/canary-svc1-pod.yaml
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/7/canary-svc2-pod.yaml
kubectl apply -f canary-svc1-pod.yaml,canary-svc2-pod.yaml

# 확인
kubectl get svc,ep,pod

# 파드 버전 확인 - svc-v1
for pod in $(kubectl get pod -o wide -l app=svc-v1 |awk 'NR>1 {print $6}'); do curl -s $pod:8080 | egrep '(Hostname|nginx)'; done

# 파드 버전 확인 - svc-v2
for pod in $(kubectl get pod -o wide -l app=svc-v2 |awk 'NR>1 {print $6}'); do curl -s $pod:8080 | egrep '(Hostname|nginx)'; done

 

 

Canary 배포를 위한 Ingress 생성

 

# Ingress 1
cat <<EOT> 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
EOT

 

# Ingress 2
cat <<EOT> 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
EOT

 

# 도메인 변경
MYDOMAIN1=kimalarm.com
sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress1.yaml
sed -i "s/kans.com/$MYDOMAIN1/g" canary-ingress2.yaml

# Ingress 배포
kubectl apply -f canary-ingress1.yaml,canary-ingress2.yaml

 

Local PC 에서 접속 테스트

 

# 100번 접속을 시도하여 연결되는 버전 확인
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr

 

 

canary-weight: "10" 의 값이 10이기 때문에 9:1 의 비율로 분배되는 것을 알 수 있습니다.

 

Canary 비율 조정 후 재시도

 

# 50 : 50 비율로 변경
kubectl annotate --overwrite ingress ingress-canary-v2 nginx.ingress.kubernetes.io/canary-weight=50

# 100번 접속을 시도하여 연결되는 버전 확인
for i in {1..100};  do curl -s $MYDOMAIN1:30080 | grep nginx ; done | sort | uniq -c | sort -nr

 

 

# 실습 종료 후 자원 삭제
kubectl delete deployments,svc,ingress --all

 


이번에는 Ingress 의 다양한 동작 방법에 대해서 공부해보았습니다.
다만, Ingress 의 여러 한계점으로 인해 Ingress 는 향후 Gateway API 로 전면 대체될 예정이라고 합니다.
앞으로 Ingress 에 대한 업데이트는 없다고 하니 Gateway API 도 공부하여 마이그레이션 하는 과정이 필요할 것 같습니다.

 

Contents

포스팅 주소를 복사했습니다