본문 바로가기
AWS/EKS

[AWS/EKS] 콘솔로 생성하는 EKS - ④ ingress로 서비스 외부 노출 시키기 (AWS LoadBalancer Controller 설치)

by 삼콩 2022. 3. 17.

본 포스팅에서는 ingress controller를 구성하고, ingress를 생성하여 Service와 연결하는 방법을 다루며 순서는 다음과 같다.

  1. Ingress 이해
  2. AWS LoadBalancer Controller 설치
  3. 매니페스트 작성 및 배포

1. Ingress 이해

개발자가 구현한 앱을 사용자에게 제공하기위해서는 외부에서 접근할 수 있는 엔드포인트가 필요하다.

쿠버네티스에서는 앱을 서비스할 수 있도록 PORT 노출, 부하 분산 기능을 가진 ServiceIngress를 제공하며 각자의 역할은 다음과같다.

  • Service: 요청 Pod에 전달 (L4)
    • ClusterIP: 클러스터 내부 접근 허용
    • NodePort: 클러스터 외부 접근 허용, 호스트IP:PORT 방식의 접근 -> IP가 노출가능할 때 사용
    • LoadBalancer: 클러스터 외부 접근 허용, 로드밸런서DNS:PORT 방식의 접근 -> AWS의 경우 기본 CLB가 생성되며, 설정시 NLB 생성 가능
  • Ingress: HTTP, HTTPS 요청을 규칙(rule)에 따라 Service에 전달 -> AWS의 경우 ALB가 생성됨 (L7)

아래 그림은 Ingress와 Service 간 구조를 나타낸 것이다. 그림을 보면 Ingress는 HTTP/HTTPS 요청과 각 규칙에 부합하는 Service로 트래픽을 전달하고, Service는 해당 트래픽을 매칭되어있는 Pod 들에 전달한다.

출처: https://medium.com/@ahmetb

 

EKS는 LoadBalancer Service 생성시 기본적으로 CLB를 생성하여 외부 서비스가 가능하도록 지원한다 (LoadBalancer Service 생성시 NLB로 생성하고 싶다면 별도의 설정 필요). EKS에서 Ingress를 생성하고자 할 경우, ingress controller가 필요하며 이 때 AWS LoadBalancer Controller를 사용한다면 ingress 생성시 ALB 구성을 지원한다.

EKS에서 LB를 구성하기 위한 사전 조건은 다음과 같다.

  • EKS Cluster가 존재해야 함
  • 다른 가용 영역에 존재하는 2개 이상의 Subnet이 존재해야 함
  • 서브넷 태그 조건 (특정 태그가 존재하는 서브넷에 LB를 위치시키기 위함) -> 
    • Private Subnet -> INTERNAL
      - key: kubernetes.io/role/internal-elb
      - value: 1
    • Public Subnet -> EXTERNAL
      - key: kubernetes.io/role/elb
      - value: 1
  • Controller v2.1.1을 사용할 경우, 서브넷에 cluster 태그 지정 필요 (본 포스팅에서는 v2.2 이상을 사용하므로 해당 과정은 수행하지 않았다)
    • - key: kubernetes.io/cluster/<cluster-name>
      - value: shared

서브넷 태그 조건은 다음 기존 포스팅의 네트워크 고려사항대로 진행하였다면 추가 할 필요 없다.

2022.01.06 - [AWS/EKS] - [AWS/EKS] 콘솔로 생성하는 EKS - ① intro, network 고려사항

 

2. AWS LoadBalancer Controller 설치

 

2.1 AWS LoadBalancer Controller 정책 생성

EKS Cluster의 AWS LoadBalancer Controller가 AWS 리소스를 다루기 위해서는 적절한 권한과 역할을 생성하여야한다. 

 

IAM > 정책 > 정책 생성 > JSON 선택 > URL 접속 후 정책 문서 복사 > 정책 생성

2.2 AWS LoadBalancer Controller 역할 생성

IAM > 역할생성> 앞선 포스팅에서 생성한 OIDC 자격증명 공급자를 선택

고객 관리형 정책 선택: 2.1에서 생성한 LoadBalancer Controller Policy 선택

2.3 신뢰관계 편집

생성된 역할에 들어가, 다음과 신뢰 관계를 편집한다.

  • 변경 전 (신뢰관계 일부)
    "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
  • 변경 후 (신뢰관계 일부)
    - region-code: 작업 리전의 코드
    - EXAMPLED539D4633E53DE1B716D3041E: OIDC 공급자 ID
"oidc.eks.region-code.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:kube-system:aws-load-balancer-controller"

신뢰관계 편집 예

 

2.4 EKS Cluster 반영

AWS LoadBalancer Controller pod가 동작시 사용할 Service Account를 생성한다.

Service Account 매니페스트 파일을 생성하고 (aws-load-balancer-controller-service-account.yaml) 아래 내용을 입력한다. 이 때 eks.amazonaws.com/role-arn annotation에 2.3에서 생성한 역할 ARN을 입력한다.

# aws-load-balancer-controller-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: aws-load-balancer-controller
  name: aws-load-balancer-controller
  namespace: kube-system
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/AmazonEKSLoadBalancerControllerRole
Service Account를 배포한다.
kubectl apply -f aws-load-balancer-controller-service-account.yaml
 

 

다음으로 Cert-Manager를 배포한다. Cert-Manager는 쿠버네티스 클러스터 내에서 TLS 인증서를 자동으로 프로비저닝, 관리하는 오픈 소스이다.

$ kubectl apply \
    --validate=false \
    -f https://github.com/jetstack/cert-manager/releases/download/v1.1.1/cert-manager.yaml

 

AWS LoadBalancer Controller 매니페스트 파일을 다운받는다. 해당 내용은 각 설치하고자하는 컨트롤러 버전마다 상이할 수 있으니 버전을 잘 확인한다. (https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3)

$ curl -Lo v2_3_0_full.yaml https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.3.0/v2_3_0_full.yaml

 

다운받은 Controller 설정 yaml 파일을 수정한다. 이 때 수정할 부분은 다음 두 가지이다.

  1. ServiceAccount 설정 삭제 (앞 단계에서 Load Balancer Controller role 역할이 적용된 서비스 계정이 이미 존재하므로 해당 부분을 제거한다. 유지시 새로운 내용이 Overwrite 된다)
    # v2_3_0_full.yaml 일부 삭제
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/name: aws-load-balancer-controller
      name: aws-load-balancer-controller
      namespace: kube-system
  2.  Deployment spec.template.spec.container.args에서 your-cluster-name 부분에 EKS 클러스터 이름 수정
...생략...
apiVersion: apps/v1
kind: Deployment
...생략...
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/name: aws-load-balancer-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/name: aws-load-balancer-controller
    spec:
      containers:
      - args:
        - --cluster-name=your-cluster-name
        - --ingress-class=alb
...생략...

 

수정된 yaml 파일 반영 및 컨트롤러 설치 확인

$ kubectl apply -f v2_3_0_full.yaml

# 컨트롤러 설치 확인
$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
aws-load-balancer-controller   1/1     1            1           14d

 

 

3. 매니패스트 배포

ingress를 배포하여 서비스와 연결하기 위해서는 서비스 할 앱이 필요하다. 간단하게 nginx를 배포하고 이를 ingress로 외부에 제공하는 과정을 다룬다.

 

3.1 Deployment 배포

먼저, Deployment를 생성한다. Deployment는 실제 앱이 존재하는 Pod와 Pod 복제본 수를 결정하는 ReplicaSet을 선언할 수 있다. 작성 과정에서 spec.selector.matchLabels의 label은 spec.template.metadata.labels 와 동일해야한다. 본 포스팅에서는 app: nginx 라는 label을 사용하였다. 만일 복수 개의 label을 사용 할 경우 모두 일치해야한다.

spec.template.spec.containers[]는 pod에 생성 될 컨테이너에 대한 내용으로, 컨테이너 이름 (name), 이미지 경로 (dockerhub, ECR 등), containerPort (서비스를 위해 컨테이너에서 open 되어야 할 포트)

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

 

Deployment를 배포하고 그 결과를 확인한다. READY 2/2로, 요청한 pod 수 2개가 정상적으로 생성됨을 확인할 수 있다.

# deployment 배포
$ kubectl apply -f nginx-deployment.yaml

# deployment, replicaset, pod 생성 확인
$ kubectl get all
NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-66b6c48dd5-5p56t   1/1     Running   0          65m
pod/nginx-deployment-66b6c48dd5-qzbtw   1/1     Running   0          65m

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           78m

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-66b6c48dd5   2         2         2       65m

 

3.2 Service 배포

다음으로 트래픽을 전달해주는 Service를 생성한다. 이 때 클러스터 외부 접근이 가능한 LoadBalancer와 NodePort 중에 NodePort type으로 생성한다. LoadBalancer를 생성하면 설정에따라 외부 서비스 노출 용으로 사용할 수 있는 AWS CLB가 생성되는데 본 포스팅에서는 이를 외부 서비스 용도로 사용하지 않고 ingress로 생성된 AWS ALB로 외부 서비스 노출 할 것이므로 NodePort를 사용한다.

spec.selector의 내용은 앞서 Deployment의 spec.selector.matchLabels, spec.template.metadata.labels와 동일해야한다.

spec.ports[]는 서비스가 요청을 전달할 때 설정할 포트에 대한 정보이다.

  • port: 외부에서 Service 접근 시 사용할 포트 (필수)
  • nodePort: pod가 존재하는 node에서 외부 접근을 위해 허용하는 port 값 (선택 사항으로, 미 지정시 30000-32767 사이 값으로 자동 할당된다)
  • targetPort: Service가 접근할 Pod의 포트. Pod에 떠있는 애플리케이션에서 접근을 허용한 port 값 들어가야 함 (default: port와 동일 값)
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - port: 80

Service를 배포하고 결과를 확인한다. nodePort가 자동으로 32112로 할당된 것을 알 수 있다. 이 단계 까지는 외부망 사용자는 nginx 애플리케이션에 접근할 수 없다. NodePort를 사용하여 서비스에 접근하려면 NodeIP:PORT가 필요하다.

본 과정에서 노드는 모두 private subnet에 존재하므로 내부IP:PORT로 접근해야한다. 따라서, 외부망에 존재하는 일반 인터넷 사용자는 아직 이 서비스에 접근할 수 없다. 

$ kubectl apply -f nginx-service.yaml

$ kubectl get svc
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.100.147.14   <none>        80:32112/TCP   5m23s

 

 

3.3 Ingress 배포

마지막으로 외부에 노출 될 ingress를 생성한다. apiVersion은 v1을 사용한다 (v1beta1의 경우, k8s 1.22 버전부터 지원하지 않는다고 하니 기억해두자). 

Ingress를 AWS ALB로 구성하기위해 metadata.annotations[]에서 최소한 다음 3개의 annotation이 필요하다. (이 방법은 최소한의 annotation으로 ALB를 생성한 것으로 다양한 annotation이 존재한다.)

  1. kubernetes.io/ingress.class: alb
  2. alb.ingress.kubernetes.io/scheme
    - internet-facing (Public IP 주소 부여됨) -> 외부망 사용자 접근을 위해 internet-facing 선택
    - internal (Private IP 주소만 존재)
  3. alb.ingress.kubernetes.io/target-type: 트래픽을 Pod로 전송하는 라우팅 방식
    - instance: 클러스터 내의 노드를 ALB 타겟으로 지정. ALB -> NodePort -> Pod (default) 
    - ip: Pod를 ALB 타겟으로 지정. ALB -> Pod
  4. alb.ingress.kubernetes.io/load-balancer-name: AWS ALB에 사용될 로드밸런서 이름

spec.rules[]에는 호스트, 경로, 쿼리에 따라 규칙을 명시할 수 있다. 본 포스팅에서는 모든 경로에대한 요청을 nginx 서비스 80포트로 전달하도록 하였다. 따라서 service.name에 앞서 생성한 service 이름(metadata.name)을 입력해야한다.

# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/load-balancer-name: my-application-load-balancer
    alb.ingress.kubernetes.io/target-type: instance
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              number: 80

 

ingress를 배포한다.

$ kubectl apply -f nginx-ingress.yaml
ingress.networking.k8s.io/nginx-ingress created

AWS 콘솔을 확인하면 아래와같이 internet-facing Application Load Balancer가 생성된것을 확인할 수 있다.

ALB 기본 구성 화면

80(HTTP)에 대한 규칙을 보면, 모든 경로(/*)에 대하여 nginx 타겟 그룹으로 전달하고 있음을 알 수 있다. 

 

타겟 그룹은 target-type annotation에 따라 달라진다.

[alb.ingress.kubernetes.io/target-type: instacne 경우]
- Service를 보면 NodePort가 32112임을 확인할 수 있다.

$ kubectl get svc
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-service   NodePort   10.100.147.14   <none>        80:32112/TCP   20m

- 타겟 그룹에는 노드 (인스턴스)가 포함되며, 포트는 NodePort와 동일하다.

 

[alb.ingress.kubernetes.io/target-type: ip 경우]
- 타겟 그룹에 pod IP가 직접 포함되며, 포트는 pod의 서비스 포트이다. 각 Pod IP는 노드가 위치한 Private Subnet IP 대역대의 값이 할당된다.

 

3.4 nginx 확인

로드밸런서의 DNS 값을 통해 인터넷 요청을하면 nginx로 잘 접근됨을 확인할 수 있다.

 

본 포스팅은 EKS에 Ingress Controller를 구성하고, 간단한 앱을 배포하고 Ingress로 외부 노출하는 방법을 다뤘다. AWS LoadBalancer Controller는 지속적으로 버전을 업그레이드하며 다양한 기능을 제공하고있다. 최근 버전들에는 ingress를 group화 해서 하나의 AWS LoadBalancer로 규칙을 정하는 방법도 가능하다. 다음 관련 포스팅에서는 이러한 방법들을 다루겠다.

 

 

[참고]

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3

https://kubernetes.io/ko/docs/concepts/services-networking/ingress/#default-backend

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/alb-ingress.html

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html

댓글