AWS Load Balancer Controller
개요
4.RESOURCE/KNOWLEDGE/AWS/AWS에서 제공하는 로드밸런서 컨트롤러.
alb, nlb 등의 ELB를 클러스터 단에서 만들 수 있도록 도와준다.
원래 이름은 AWS ALB Ingress Controller였는데, 이름을 바꿨다.
이름에서 알 수 있듯이, 이 녀석은 Ingress와 Service#LoadBalancer를 만드는 데에 도움을 준다.
서비스, 인그레스 오브젝트를 만들 때 어노테이션으로 관련 설정을 넣어주면 이를 반영하여 aws에 관련 리소스가 만들어지게 된다.
동작 원리
AWS 로드 밸런서 생성 순서
인그레스 생성 시의 그림인데, 대충 과정에 대한 설명이 나와있다.
먼저 ALB 컨트롤러는 kube-apiserver의 인그레스, 서비스 관련 이벤트를 추적(watch)하고 있다.
그러다 특정 어노테이션이 붙은 인그레스, 서비스가 들어온다면 본격적으로 조작을 시작한다.
해당 컨트롤러는 AWS에 api 통신을 하며 이때 요구 사항에 맞춰 AWS LB를 만들어주게 된다.
인그레스 - ALB
인그레스를 생성할 시, ALB를 만들어준다.
- 룰은 인그레스에 설정된 룰이 들어간다.
- 타겟그룹에는 백엔드로 명시된 서비스가 들어간다.
- 클러스터에 해당 타겟 그룹에 대한 정보를 담는 TargetGroupBinding이라는 오브젝트를 새로 따로 생성해준다.
- 이를 통해 클러스터에서 편하게 바인딩된 타겟 그룹의 정보를 확인할 수 있다.
- 리스너도 만들어지는데, 어노테이션으로 포트 설정을 하지 않을 시 443, 80이 설정된다.
인그레스가 삭제되면 실제로 aws의 lb를 삭제시켜준다.
로드 밸런서 - NLB
로드 밸런서를 생성할 때는 NLB를 만들어준다.
이 놈은 그냥 충실하게 그대로 만들어져서 뭐 굳이 말할 거리는 없는 것 같다.
이 친구도 TargetGroupBinding이라는 오브젝트가 만들어진다.
권한
여기에서 AWS를 조금 안다면 궁금증이 생길 수 있다(적어도 나는 그랬다).
어떻게 컨트롤러는 AWS 리소스를 조작하는 것일까?
당연히 api를 사용하는 걸텐데, 어떻게 권한을 획득하는 가가 궁금해지는 부분이다.
이는 컨트롤러 파드에 IAM 권한을 부여하는 IRSA 방식이나 파드가 위치한 노드에 대한 파드 identity 세팅을 통해 이뤄진다.
다시 말해 로드밸런서 컨트롤러 파드의 서비스 어카운트에 대한 IAM 설정이 필요하다는 것이다.
여기에 추가적으로, 만들어진 로드밸런서는 파드로 트래픽을 어떻게 전달하는가?
보안그룹에 걸리지는 않는가?
alb 컨트롤러는 만들어지는 모든 로드 밸런서에 두 가지의 보안 그룹을 추가한다.
첫번째는 모든 로드 밸런서가 가지는 보안그룹으로, 아무런 인바운드가 없다.
두번째는 해당 로드밸런서가 트래픽을 받을 포트의 인바운드를 허용하는 보안그룹이다.
이 중 첫번째 보안 그룹 덕분에 클러스터로 통신하는 것이 가능해지는데 클러스터 보안그룹의 인바운드에 어느샌가 해당 보안그룹을 허용하는 인바운드 룰이 생기게 된다.
이벤트로 해당 보안 그룹이 만들어지는 것까지는 확인했는데, 내가 잘못 찾은 건지 저 룰을 추가하는 이벤트를 확인하지는 못했다.
나중에 기회되면 다시 시도해볼 듯.
이를 활용하기 위해서는 클러스터의 파드가 aws의 lb api를 호출할 수 있도록 적절한 권한 세팅이 필요하다.
아래에서 다루겠다.
기능
접근 유형
원래 aws의 로드밸런서가 그러하듯이 사용자는 로드밸런서가 internal(내부용)인지 internet-facing(외부용)인지 세팅할 수 있다.
annotations:
alb.ingress.kubernetes.io/scheme: internal
이렇게 scheme 어노테이션을 다는 식으로 세팅한다.
타겟 모드
로밸컨은 두 가지 타겟 그룹을 둘 수 있다.
ALB에서 두는 타겟 그룹 세팅을 기반으로 하는 기능이라 보면 되겠다.
Instance mode
흔한 로드밸런서의 동작과 같다.
로드밸런서라는 서비스는 사실 외부의 로드밸런서의 ip로 들어오는 요청에 대해 노드포트 규칙에 대응하게 하는 것밖에 없다.
클러스터로 들어가는 트래픽은 노드 포트를 통해 이뤄지며, 이쪽으로 트래픽을 흘려보내는 것을 외부 로드밸런서에 위임하는 것이 바로 로드밸런서 서비스이다.
instance 모드는 그냥 이 방식에 충실한 모드이다.
트래픽 정책에 대한 설정이 없다면 노드로 들어온 트래픽은 내부 iptables 규칙에 따라 분산된다.
그래서 기껏 로드밸런서가 트래픽을 분산해주는데 클러스터 내부로 와서 또 분산되는 꼴이 된다.
기껏 트래픽을 잘 분산시켰다고 생각했는데 내부적으로 또 분산을 해서 괜히 불필요한 트래픽이 발생할 가능성이 높다.
IP mode
이건 로드밸런서만 정확하게 분산을 해주는 방식이다.
어떻게 하느냐, 로드밸런서가 실제 트래픽을 받아야 하는 파드의 ip로 실제로 라우팅을 해주어 중간 단계를 거치지 않고 바로 트래픽을 쏴버리는 방식인 것이다.
조건
이를 사용하기 위한 몇 가지 조건이 있다.
- VPC CNI
- [[#IP mode]] 사용을 위해, 파드가 노드와 같은 대역의 ip를 할당 받아야 한다.
- IAM 정책
- 노드의 컨트롤러가 로드밸런서를 만들어야 하니 해당 파드, 혹은 노드에 정책이 필요하다.
- 파드에 정책을 부여할 거라면 IRSA를 설정해야 할 것이고, 아니면 노드에 정책을 부여하면 된다.
- 서브넷 태그
- 인터넷과 내부용 구분을 지을 때 서브넷 자동 탐색 지원하기 위해 필요하다.
- 참고로 원래는
kubernetes.io/cluster/${cluster-name}: owned|shared
태그도 필요한데,SubnetsClusterTagCheck=false
feature gate 설정을 통해 넘어갈 수 있다.
- 9443 포트 개방
- 노드들은 컨트롤 플레인에서 오는 9443 tcp 인바운드에 열려있어야 한다.
- 웹훅 접근을 하기 위해서다.
curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
관련한 정책은 이 파일을 참고하자.
설치
helm repo add eks https://aws.github.io/eks-charts
간단하게 Helm 씁시다 좋잖아요 네?
# irsa를 쓰는 경우
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=<cluster-name> --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
# 그냥 노드에 정책을 부여하는 경우
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=<cluster-name>
이 세팅은 파게이트에서도 유효하고 하는데, 이건 추가적으로 확인해봐야겠다.
세팅
세팅은 거의 어노테이션을 다는 것으로 좌지우지 된다.
어노테이션이 설정의 유연성을 제공하고, 또한 다양한 환경과 툴 사이에서 커스텀하기는 좋을 것이다.
그러나 이 방식은 유효성 검사를 어렵게 하는 측면이 있어 장차 따로 CRD로 만들어서 양식을 제공하는 것이 좋지 않을까 한다.
어노테이션을 계속 활용하기 위해 Admission Control을 따로 세팅하게 만든다?
이건 기존 방식을 유지하기 위해 그저 상황을 더 복잡하게 만드는 게 아닐까..
양식을 작성하는 시점에 현재 양식의 문제점을 알 수 없게 하는 것은 많은 오류를 야기할 수 있다.
라벨을 달거나 CRD를 만드는 경우도 있기는 하지만, 대체로는 어노테이션으로 처리되고, 기능이 엄청 많다..
필요한 기능은 직접 문서[1]에서 찾아서 세팅하는 것이 적절하겠다.
여기에서는 대표적인 것들만 몇 개 정리하고자 한다.
--ingress-class
인자에 사용하고 싶은 ingress class 이름을 지정하면 되는데, 기본은 alb이다.
인그레스 관련 세팅
먼저 각 리소스들에 대해서 어떻게 세팅할 수 있는지 보자.
인그레스 그룹
alb.ingress.kubernetes.io/group.name: my-team.group
인그레스들을 통합적으로 관리하고자 할 때 인그레스 그룹을 둘 수 있다.
이 세팅을 하면 실제 aws에서는 같은 그룹끼리 하나의 ALB에 묶어준다.
또한 각 인그레스에 이뤄진 세팅들을 묶어주기까지 한다.
다만 서로 충돌이 아는 세팅들이 있을 수 있는데, 이것은 문서에 Exclude
라고 표시가 되어 있으니 참고.
타겟 모드
alb.ingress.kubernetes.io/target-type: ip
인그레스의 타겟 모드를 지정할 때는 이렇게 어노를 단다.
리스너 세팅
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}, {"HTTP": 8080}, {"HTTPS": 8443}]'
리스터 포트 세팅은 이렇게 여러 개를 지정할 수 있다.
쿠버 로드밸런서를 만들 때는 포트를 이렇게 어노로 세팅할 필요가 없는데 인그레스는 조금 귀찮다..
기본적으로 인그레스는 원래 http 트래픽을 대상으로 만들어진 것이라 직접적으로 포트를 커스텀할 수 없다.
그러나 앞단의 로드밸런서에서 이렇게 세팅이라도 해주면 그나마 원하는 포트만 열어둠으로써 공격표면을 줄이는데 도움이 될 것이다.
접근 유형
alb.ingress.kubernetes.io/scheme: internal
scheme을 지정해서 내부인지, 인터넷으로 나가는지 지정할 수 있다.
헬스체크
alb.ingress.kubernetes.io/healthcheck-protocol: HTTPS
alb.ingress.kubernetes.io/healthcheck-port: '80'
alb.ingress.kubernetes.io/healthcheck-path: /ping
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '10'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '8'
alb.ingress.kubernetes.io/success-codes: 200-300
트래픽 분배
alb.ingress.kubernetes.io/actions.blue-green: |
{
"type":"forward",
"forwardConfig":{
"targetGroups":[
{
"serviceName":"hello-kubernetes-v1",
"servicePort":"80",
"weight":100
},
{
"serviceName":"hello-kubernetes-v2",
"servicePort":"80",
"weight":0
}
]
}
}
어지러운 어노테이션..
둘 이상의 타겟 그룹에 대해 트래픽을 가중치 분산하고 싶을 때는 이렇게 설정한다.
당연히 이렇게 세팅하는데 아무런 문제가 없냐 싶을 것이다.
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blue-green
port:
name: use-annotation
그래서 이렇게 인그레스를 세팅할 때는 백엔드의 서비스 이름이 alb.....io/actions.{이름}
과 일치해야 하고, port는 정확하게 use-annotation
이라고 지정해줘야만 한다.
인그레스가 이따구니까 게이트웨이API가 나오는 거다.
이외
잡다한 게 많으니 간단하게만..
특정 에러에 대한 대응 방식을 이렇게 json형식으로 작성할 수 있다.
alb.ingress.kubernetes.io/security-groups: <sg-id>
미리 커스텀한 보안 그룹을 통해 로드밸런서로 들어오는 트래픽을 제한하고 싶을 때는 이렇게 세팅해주면 된다.
아래에 쓰겠지만, 서비스를 만들 때도 이런 방식이 가능하다.
인그레스 클래스 세팅
인그레스클래스를 직접 만드는 것도 가능하다.
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: awesome-class
spec:
controller: ingress.k8s.aws/alb
parameters:
apiGroup: elbv2.k8s.aws
kind: IngressClassParams
name: awesome-class-cfg
인그레스클래스는 쿠버의 기본 오브젝트이지만, alb controller는 한 술 더 떠서 인그레스 클래스에 대한 파라미터들을 따로 CRD로 만들어서 관리하는 게 가능할 수 있도록 해뒀다.
apiVersion: elbv2.k8s.aws/v1beta1
kind: IngressClassParams
metadata:
name: awesome-class
spec:
scheme: internal
ipAddressType: dualstack
group:
name: my-group
tags:
- key: org
value: my-org
위에서 내가 한 생각을 이미 옮긴 건가..
아무튼 요런 식으로 어노테이션으로 달기만 해서 항상 불안했던 세팅들을 양식으로 명시적으로 관리하는 게 가능하다!
로드밸런서 관련 세팅
apiVersion: v1
kind: Service
metadata:
name: echoserver
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
spec:
selector:
app: echoserver
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
참고로 EKS에서 로드밸런서를 만들면 cloud controller manager를 통해 CLB 로드밸런서를 만들어준다.
그래서 로밸컨으로 세팅하고 싶다면 스펙에 loadBalancerClass
를 명시해줘야 한다.
타겟 모드
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
이외
service.beta.kubernetes.io/aws-load-balancer-security-groups: <sg-id>
이것도 들어오는 인바운드 트래픽에 대해 적용하고 싶은 보안 그룹을 넣는 필드이다.
SSL Redirect
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:xxxx:certificate/xxxxxx
alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-1-2017-01
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
인그레스에 대한 세팅이다.
리다이렉트를 시키려면 위처럼 두 포트를 무조건 열어줘야 한다.
참고로 TLS에 대한 추가적인 정보가 제시되지 않더라도, 443 등의 포트로 통신하는 경우 기본적으로 해당 호스트에 대해 alb는 인증서 관련 정보를 찾게 된다.
TargetGroupBinding
레디네스 게이트
파드 라이프사이클에서 레디네스 게이트 부분 관련 설정은 어떻게 하는가?
elbv2.k8s.aws/pod-readiness-gate-inject: enabled
이 값을 네임스페이스에 라벨로 달아주면 로드 밸런서의 헬스체크까지 고려하여 백엔드에 트래픽을 전달하는 세팅을 할 수 있게 된다.
이 세팅은 당연하지만, [[#IP mode]]에서만 의미가 있다.
인스턴스 모드에서는 어차피 로드밸런서가 인스턴스의 상태만 고려하고 트래픽을 보내기 때문이다.
파드에는 target-health.elbv2.k8s.aws
라는 접두사가 붙은 상태가 추가되며 이 값으로 상태를 알 수 있다.
레디네스 게이트는 파드에 대해 -o wide
옵션을 넣어서도 확인할 수 있다.
참고로 위의 라벨을 쓰고 싶지 않다, 혹은 해당 네임스페이스에서 특정 파드들에 대해서만 이를 적용하고 싶다면, Admission Webhook을 수정해주면 된다.
kubectl edit mutatingwebhookconfigurations aws-load-balancer-webhook
...
name: mpod.elbv2.k8s.aws
namespaceSelector:
matchExpressions:
- key: elbv2.k8s.aws/pod-readiness-gate-inject
operator: In
values:
- enabled
objectSelector:
matchLabels:
elbv2.k8s.aws/pod-readiness-gate-inject: enabled
여기에서 objectSeletor
부분을 원하는 값으로 바꿔주면, 파드 별로 설정할 라벨을 지정할 수 있게 된다.
관련 문서
이름 | noteType | created |
---|---|---|
AWS Load Balancer Controller | knowledge | 2025-02-12 |
2주차 - 네트워크 | project | 2025-02-25 |