kOps 클러스터에서 LoadBalancer와 Ingress 배포 (feat. AWS Load Balancer Controller)
이번에는 kOps를 통해 생성한 클러스터에 LoadBalancer와 Ingress를 각각 생성해보도록 한다.
LoadBalancer와 Ingress 서비스는 AWS 클라우드 환경에 배포하는 경우 각각 NLB, ALB와 연동이 된다.
즉, 모두 AWS 로드밸런서 서비스(ELB)와 연결이 된다고 볼 수 있다.
클러스터의 서비스를 이러한 클라우드 로드밸런서와 연결하기 위해서 컨트롤러를 설치해줄 필요가 있다.
AWS의 경우 AWS Load Balancer Controller를 설치해주어야한다.
AWS Load Balancer Controller (LBC)
Kubernetes 클러스터 내의 LoadBalancer와 Ingress 서비스를 각각 AWS NLB, ALB로 연결해주는 Controller
이 LBC는 클러스터 내부의 LoadBalancer와 Ingress 서비스를 바탕으로 NLB와 ALB를 클라우드 환경에 자동으로 프로비저닝해준다.
LBC는 클러스터 내에 pod 형태로 배포되며 LoadBalancer와 Ingress 서비스에 연결된 pod에 바로 연결할 수 있도록 pod의 IP주소를 모니터링 후, 클라우드 환경에 프로비저닝된 NLB/ALB에 그 정보를 제공한다.
이 LBC는 오픈소스로 관리되며 Helm과 eksctl로도 설치가 가능하다.
Load Balancer Controller 설치
EKS 환경의 경우, IRSA를 적용한 뒤 helm을 통해 Load Balancer Controller를 바로 설치하는 것을 권장한다.
우리가 사용하는 kOps 환경에서는 Load Balancer Controller를 add-on 방식으로 지원한다.
add-on 방식으로 Load Balancer Controller를 설치하는 경우, 클러스터 spec에서 cert-manager와 aws-load-balancer-controller를 활성화시켜주어야한다.
kops cluster edit --name [cluster명]
cluster YAML문 중 spec 구문 하위에 cert-manager를 먼저 설치해준다.
spec:
api:
loadBalancer:
class: Network
type: Public
certManager: # cert-manager 먼저 적용하고, 그 다음에 LBC 적용
enabled: true
# cluster edit 후 kops update cluster -y
+) aws-load-balancer-controller와 cert-manager를 동시에 enable 시켜주는 경우, dependency의 문제인지, aws-load-balancer-controller가 cert-manager의 secret을 가져오지 못해서 pod가 올라가지 못하는 문제가 발생한다. (`secret "aws-load-balancer-webhook-tls" not found`)
(이거 해결하느라 개고생;;)
그리고 2개를 동시에 enable시켜서 생성이 멈춘 controller pod를 cluster edit을 통해 다시 비활성화 시켜도 이 pod와 cert-manager 관련 pod들은 내려가지 않는다.
직접 deployment들을 일일히 삭제시키거나 cluster를 지우고 다시 만들어야한다.
이 문제와 관련된 issue를 여기서 확인할 수 있다.
(나온지 한참된 dependency 버그같은데, kOps 공식 Docs에도 그렇고 fix나 reference가 없다...)
아무튼 cert-manager 설치가 완전히 된 것을 확인 후, aws-load-balancer-controller를 enable시켜준다.
마찬가지로 spec 구문 하위에 작성해준다.
spec:
api:
loadBalancer:
class: Network
type: Public
awsLoadBalancerController: # 클러스터에 aws-load-balancer-controller가 설치됨
enabled: true
certManager: # cert-manager 먼저 적용하고, 그 다음에 LBC 적용
enabled: true
이렇게 kOps 클러스터에 aws-load-balancer-controller까지 올라가면, 비로소 LoadBalancer와 Ingress 서비스를 올릴 준비가 된 것이다.
LoadBalancer
먼저, 간단한 LoadBalancer 서비스를 하나 올려본다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: echo-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
selector:
app: deploy-websrv
metadata의 annotation에 연결할 Load Balancer의 설정값들을 담는다.
- nlb-target-type: NLB에 연결될 타겟의 타입 (ip, instance)
- ip 모드의 경우, NLB에서 내부의 pod IP로 바로 연결하게 된다.
- instance 모드의 경우, NLB에서 instance(NodePort)를 거쳐 pod로 연결하게 된다.
- scheme: 생성할 NLB의 종류 (internet-facing / internal)
- healthcheck-port : Load Balancer와 연결할 Service의 포트 (healthcheck 용도)
LoadBalancer를 생성하면 External IP로 우리가 접속할 Load Balancer URL이 발급된 것을 볼 수 있다.
발급된 Load Balancer URL로 접속하면 내부에 배포한 pod의 Application에 접근할 수 있는 것을 알 수 있다.
해당 Load Balancer에 연결된 Target Group(대상 그룹) 내용을 확인하면 2개의 IP를 볼 수 있다. 이들은 모두 pod의 IP이다. 이를 통해 Load Balancer Controller를 통해 내부 pod의 IP정보가 Load Balancer로 전달/관리가 되는 것을 알 수 있다.
VPC CNI를 사용함으로써 얻을 수 있는 이점인 것이다.
LBC의 NLB 관련 annotation은 아래 공식 Docs에서 확인 가능하다.
Ingress
다음으로 Ingress를 생성해본다.
간단한 flask 웹 Application으로 배경색상을 다르게 설정해 2개의 Service로 생성해 주었다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: blue-deploy
labels:
app: blue
spec:
replicas: 1
selector:
matchLabels:
app: blue
template:
metadata:
labels:
app: blue
spec:
containers:
- name: blue
image: kodekloud/webapp-color
env:
- name: APP_COLOR
value: blue
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: blue
spec:
type: ClusterIP
selector:
app: blue
ports:
- port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: green-deploy
labels:
app: green
spec:
replicas: 1
selector:
matchLabels:
app: green
template:
metadata:
labels:
app: green
spec:
containers:
- name: green
image: kodekloud/webapp-color
env:
- name: APP_COLOR
value: green
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: green
spec:
type: ClusterIP
selector:
app: green
ports:
- port: 80
targetPort: 8080
그리고 이 Service 2개를 묶을 Ingress를 생성한다. 두 서비스는 path를 기반으로 나누어 접속되도록 설정해본다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: colors
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /blue # /blue path는 blue service로
pathType: Prefix
backend:
service:
name: blue
port:
number: 80
- path: /green # /green path는 green service로
pathType: Prefix
backend:
service:
name: green
port:
number: 80
Ingress를 연결한 ALB가 프로비저닝될때까지 대기
aws elbv2 describe-load-balancers --region ap-northeast-2 | jq
하지만 이렇게 설정 후 ALB에 접속하면 404 에러가 발생하는 것을 볼 수 있다.
URL Rewrite
각 pod들의 로그를 확인하면, Ingress에 설정한 Path대로 ALB에 접속하면 각 pod들로 맞게 리다이렉션은 되는 것을 확인할 수 있다.
테스트로 가져온 이미지는 root path에 대한 경로만 존재해, ALB에서 각 rule의 path(/blue, /green)으로 들어온 요청을 처리하지 못한 것이다.
즉, 클라이언트의 요청 경로와 백엔드 서비스가 기대하는 경로가 일치하지 않아 발생한 문제인 것이다.
이 문제를 해결하기 위해서는 2가지 방법을 생각할 수 있다.
- Ingress의 설정 중 rewrite-target을 root path('/')로 설정해주는 것
- 각 pod의 Application의 경로들을 수정
가장 정석적인 방법은 ingress의 rewrite-target을 루트로 설정해주는 것이다.
rewrite-target은 클라이언트가 특정 경로로 요청을 보낼때, 이를 다른 경로로 리다이렉트하거나 변경하는데에 사용되는 옵션이다.
하지만 AWS LBC는 rewrite-target 옵션 설정이 존재하지 않는다.
그렇기에 보통 Nginx Ingress controller를 이용해 rewrite-target 옵션을 설정한 ingress를 배포한다고 한다.
이번 글에서 Nginx Ingress Controller까지 다루기에는 분량이 넘치는 관계로 AWS LBC를 사용하기 위해 Application을 약간 수정한 image를 사용하기로 한다.
kodekloud/webapp-color → omoknooni/webapp-color
Group을 통한 여러 Ingress에 대한 ALB 공유
이번에는 총 2개의 ingress를 생성하고, 이 둘이 하나의 ALB를 사용해 접속할 수 있도록 구성해보도록 한다.
새로 만들 ingress에는 앞서 사용한 Echo-server(ClusterIP 서비스 추가)와 Red Color 서비스를 연결해보도록 한다.
이 구성의 핵심은 하나의 ALB를 사용하도록 2개의 ingress가 동일한 group.name을 사용하는 것이다.
동일한 group.name을 지정함을 통해 다수의 ingress들의 rule들이 merge되고, 하나의 ALB로 서비스된다.
(공식 Docs에서는 이를 "explict IngressGroup"으로 칭한다.)
앞서 생성한 ingress에도 group.name annotation을 작성해준다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: colors2
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: main-group
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /red # /red path는 blue service로
pathType: Prefix
backend:
service:
name: red
port:
number: 80
- path: /echo # /echo path는 echo-service로
pathType: Prefix
backend:
service:
name: echo-service
port:
number: 80
2개의 ingress를 배포한 뒤 내용을 확인하면 모두 같은 Address를 부여받은 것을 확인할 수 있다.
같은 ALB Address를 통해 2개의 Ingress에 연결한 Service에 접근이 가능한 것을 확인할 수 있다.
LBC의 ALB 관련 annotation은 아래 공식 Docs에서 확인 가능하다.