지금까지
아래 과정을 수행하였습니다.
이번 주차에는
aws-load-balance-controller를 활용한 Ingress와 IngressClass등에 대해서 딥다이브를 진행합니다.
ALB Ingress
K8s 외부 리소스(e.g. ELB)와 Ingress 등을 생성/관리/삭제 하기 위해서 IngressClass 중 하나인 aws-load-balancer-controller를 설치하였습니다.
이 경우 일반적으로 아래와 같이 트래픽이 흐릅니다.
ALB : Ingress : Svc는 1 : 1 : 1로 매칭됩니다.
Traffic Flow : ALB → Ingress → Svc → Pod .. Dep
ALB Ingress - Context Based Routing
하지만 Context Based Routing 기능을 사용하여
ALB : Ingress : SVC가 1 : 1 : N으로 매핑되게 할 수 있습니다.
Traffic Flow : ALB → Ingress → SVC-1, SVC-2, …, SVC-N
ALB Ingress - Context Based Routing Manifest
AppName1, AppName1 라는 Deployment가 있을 때,
/app1 → AppName1, /app2 → AppName2에 대한 Context Based Routing Ingress 설정이 가능합니다. [Ref]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: <IngressName>
annotations:
## Metadata
alb.ingress.kubernetes.io/load-balancer-name: <ELBName>
alb.ingress.kubernetes.io/scheme: internet-facing
## Health Check
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-port: traffic-port
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '15'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5'
alb.ingress.kubernetes.io/success-codes: '200'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'
spec:
ingressClassName: <IngressClassName>
rules:
- http:
paths:
- path: /app1
pathType: Prefix
backend:
service:
name: <AppName1>
port:
number: 80
- path: /app2
pathType: Prefix
backend:
service:
name: <AppName2>
port:
number: 80
Ingress를 배포한 다음 아래 명령어를 통해서 확인할 수 있습니다.
kubectl get ingress -A
ALB Ingress - Context Based Routing, Root Context
K8s Ingress의 .spec.rules[*].http.paths[*].path는 위에서 아래로 적용되며, 위에 있는 것이 우선순위를 가집니다. 아래는 Bad Case 예시이며 모든 요청은 첫번째 path로 라우팅됩니다.
- path: /*
- path: /app1
- path: /app2
따라서 와일드 카드로 정의되는 Root Context(/*)는 맨 마지막에 적어야 합니다.
- path: /app1
- path: /app2
- path: /app2
ALB Ingress HTTPS
ALB Ingress - SSL 준비하기
ALB Ingress - SSL Annotation
ALB Ingress - DNS A Record
ALB Ingress - SSL 준비하기
위에서 배운 방식으로 생성한 AWS ELB는 HTTP Protocol을 사용합니다.
이때 HTTPS Protocol을 사용하기 위해서 SSL 관련 사전 준비가 필요합니다.
AWS Route53 DNS 도메인 등록하기
AWS Certificate Manager로 SSL Certificate 발급받기
Ingress에 SSL Annotation 추가하기
K8s Manifest 배포하고 테스트하기
위 중에서 1,2를 별도로 진행해주세요.
ALB Ingress - SSL Annotation
Ingress의 .metadata.annotations.alb.ingress.kubernetes.io/certificate-arn 에 ACM으로 발급받은 SSL Certificate의 arn을 등록할 수 있습니다. → [Ref]
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:<ACCOUNT>:certificate/<ACM_ID>
또한 HTTPS 설정을 해야 하므로 아래와 같이 주요 값들을 추가로 변경해줍니다.
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-1-2017-01 #Optional (Picks default if not used)
ALB Ingress - DNS A Record
생성한 AWS ELB의 사용을 위해서 Route53에 A Record를 생성하여 연동해야 합니다. 추후 이 방법은 ExternalDNS를 사용하는 방법으로 고도화 가능합니다.
ALB Ingress - Redirect to HTTPS
http://naver.com으로 요청을 보내더라도 https://naver.com으로 redirect 시킬 수 있습니다. 이 기능을 위해 Ingress에 다음 .metadata.annotation을 추가해주세요.
alb.ingress.kubernetes.io/ssl-redirect: '443'
ExternalDNS
앞서 ALB에 대한 Route53 A Record 설정은 수동으로 진행하였습니다.
하지만 ExternalDNS를 설치 후 사용하면 이 방식을 자동화할 수 있습니다.
ExternalDNS
ExternalDNS를 사용하면 AWS Route53의 레코드를 K8s Object를 통해서 제어할 수 있습니다. 이 과정에서 필요한 권한은 ServiceAccount를 통해서 할당합니다.
AWS 사전 준비 항목
Route53 Hosted Zone
IAM Role(with Policy)
K8s 구성 요소
ServiceAccount : Deployment에 IAM Role을 할당하기 위해서 사용
Deployment : external-dns-pod 들을 블루그린 배포
ClusterRole : Cluster Level에 리소스에 대한 권한을 정의
ClusterRoleBinding : ClusterRole을 ServiceAccount에 부여하여 실제로 Cluster 접근을 허용
IAM Policy & ServiceAccount
ExternalDNS를 사용하기 위한 최소 권한의 IAM Policy가 있습니다. → [Ref]
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": [
"*"
]
}
]
}
이후 다음 명령어를 이용해서 AWS EKS IAM ServiceAccount를 생성할 수 있습니다.
eksctl create iamserviceaccount \
--name service_account_name \
--namespace service_account_namespace \
--cluster cluster_name \
--attach-policy-arn IAM_policy_ARN \
--approve \
--override-existing-serviceaccounts
이렇게 생성한 ServiceAccount는 Deployment를 통해서 Pod에 연결할 수 있습니다.
spec:
serviceAccountName: service_account_name
containers:
- name: external-dns
image: k8.sgcr.io/external-dns/external-dns:v0.10.2
ExternalDNS Manifest
ServiceAccount
apiVersion: v1 kind: ServiceAccount metadata: name: external-dns annotations: eks.amazonaws.com/role-arn: <role arn>
ClusterRole
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list","watch"]
ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: default
Deployment
apiVersion: apps/v1 kind: Deployment metadata: name: external-dns spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.10.2 args: - --source=service - --source=ingress - --provider=aws - --aws-zone-type=public - --registry=ycy # - --domain-filter=external-dns-text. ... # - --policy=upsert-onlyy - --txt-owner-id=<Owner ID> securityContext: fsGroup: 65534
ALB Ingress Advanced
AWS EKS aws-load-balancer-controller 배포하기에서 학습한 내용입니다.
20219년에 추가된 ALB Advanced Request Routing 기술을 사용하면 ALB : Ingress : SVC = 1 : 1 : N 에서 복수의 Traffic Flow를 분리할 수 있습니다.
즉,
https://naver.com으로 들어오면 SVC-1으로 https://www.naver.com으로 들어오면 SVC-2로 트래픽을 라우팅할 수 있습니다. → [Ref]
ALB Ingress Manifest
다음과 같이 ALB Ingress를 제어할 수 있습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: <IngressName>
annotations:
## Metadata
alb.ingress.kubernetes.io/load-balancer-name: <ALBName>
alb.ingress.kubernetes.io/scheme: internet-facing
## Health Check
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-port: traffic-port
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '15'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5'
alb.ingress.kubernetes.io/success-codes: '200'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'
## HTTPS
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: <ACMCertificateARN>
alb.ingress.kubernetes.io/ssl-redirect: '443'
external-dns.alpha.kubernetes.io/hostname: default101.stacksimplify.com
spec:
ingressClassName: my-aws-ingress-class
defaultBackend:
service:
name: app3-nginx-nodeport-service
port:
number: 80
rules:
- host: app101.stacksimplify.com # 🎁 KeyPoint
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app1-nginx-nodeport-service
port:
number: 80
- host: app201.stacksimplify.com # 🎁 KeyPoint
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app2-nginx-nodeport-service
port:
number: 80
SSL Discovery with Host & TLS
지금까지는 alb.ingress.kubernetes.io/certificate-arn을 명시적으로 선언해주었습니다. 하지만 해당 annotations을 지정하지 않더라도 IngressController는 .spec.rules[*].host 값을 기준으로 TLS/SSL Certificate를 자동으로 탐색하게 됩니다.
즉 IngressController는 Ingress .spec.tls.hosts 혹은 .spec.rules[*].host 값 중에서 TLS Certificate를 찾으려고 시도합니다.
단, Listener Ports와 관련된 .alb.ingress.kubernetes.io/listen-ports: ‘[{“HTTPS”: 443}]’이라는 .metadata.annotaion을 추가하여 HTTPS 리스너를 사용하도록 명시적으로 지정해야 합니다.
SSL Discovery with Host
.alb.ingress.kubernetes.io/listen-ports가 없더라도 아래와 같이 .spec.rules[*].host 를 기반으로 SSL Certificate를 가져오게 됩니다.
이 방식을 사용하기 위해서는 .alb.ingress.kubernetes.io/certificate-arn 을 주석처리 하여야 합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: <IngressName>
annotations:
## Metadata ...
## Health Check ...
## HTTPS ...
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
spec:
ingressClassName: # ...
rules:
- host: app101.stacksimplify.com # 🎁 KeyPoint
- host: app102.stacksimplify.com # 🎁 KeyPoint
SSL Discovery with TLS
혹은 조금 더 명시적으로 .spec.tls.hosts를 기반으로 SSL Certificate를 가져오도록 명시할 수도 있습니다.
이 방식을 사용하기 위해서는 .alb.ingress.kubernetes.io/certificate-arn 을 주석처리 하여야 합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: <IngressName>
annotations:
## Metadata ...
## Health Check ...
## HTTPS ...
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
spec:
ingressClassName: # ...
tls:
- hosts:
- *.stacksimplify.com
rules:
- host: app101.stacksimplify.com
- host: app102.stacksimplify.com
IngressGroup
IngressGroup이 없어도 Ingress를 사용해서 다양한 워크로드를 배포할 수 있습니다. 하지만 Multi Svc을 단일 Ingress로 배포하게 된다면 Manifest 파일은 거대해지고 관리하기가 매우 어려워지는 문제가 있습니다.
따라서 Multi Svc에 개별적으로 Ingress를 할당하고
IngressGroup을 통해서 이 Ingress들을 묶어서 연결할 수 있습니다.
AS-IS | ALB : Ingress : SVC = 1 : 1 : N
TO-BE | ALB : IngressGroup : Ingress : SVC = 1 : 1 : N : N
IngressGroup Features
IngressGroup의 주요 기능은 다음과 같습니다.
IngressGroup 기능으로 여러 Ingress를 그룹화할 수 있습니다.
IngressGroup 기능으로 여러 Ingress들에 대해서 Priority를 지정할 수 있습니다.
IngressController는 IngressGroup내의 모든 Ingress에 대한 Ingress Rule을 자동으로 병합하고 단일 ALB로 이를 지원합니다.
Ingress에 정의된 대부분의 Annotations은 해당 Ingress에 정의된 경로에만 적용됩니다.
IngressGroup Manifest
IngressGroup은 개별 Ingress에 alb.ingress.kubernetes.io/group.name 등의 .metadata.annotation을 할당함으로서 작동합니다. → [Ref]
alb.ingress.kubernetes.io/group.name: myapps.web
alb.ingress.kubernetes.io/group.order: 30
ALB Ingress Target Types
ALB가 Target에 대해서 Traffic Routing을 진행하는 방식은 크게 Instance 방식과 IP 방식이 존재합니다. 따라서 alb.ingress.kubernetes.io/target-type 이라는 annotation을 사용하여 해당 방식을 지정할 수 있습니다. 이 방식은 traffic이 pods에 route되는 방식을 결정합니다.
Instance vs Ip
Instance Mode
Instance Mode는 서비스를 위해 열린 NodePort의 모든 ec2 인스턴스로 Traffic을 Route합니다.
Instance Mode를 사용하려면 서비스 유형이 NodePort 또는 LoadBalancer 여야 합니다.
기본 alb.ingress.kubernetes.io/target-type은 instance mode이므로 별도로 지정할 필요는 없습니다.
alb.ingress.kubernetes.io/target-type: instance
Ip Mode
Ip Mode는 traffic을 pod ip로 직접 라우팅합니다.
Ip Mode는 ALB에서 sticky session이 필요한 경우에 사용할 수 있습니다.
기본 alb.ingress.kubernetes.io/target-type을 재정의 하여야 합니다.
더 알아보기
기존에 알고 있던 IngressController 외에도 다양한 내용을 추가로 알 수 있었습니다. 그런데 몇 가지 궁금한 부분이 있어서 더 공부해보았습니다.
Networking 사전 지식
AWS EKS에서는 Pod에 IP를 할당하기 위해서 다음의 2가지 방식 중 하나가 사용됩니다.
Secondary IPv4 Address (default)
Prefix Mode (Linux/Windows)
EKS Prefix Mode/Delegation #기본값에서 살펴본 바와 같이,
EKS 에서는 개별 Pods들에 대해서 Private IPv4 Address가 할당됩니다.
Network 측면에서 Instance 와 Ip 방식 비교
위에서 배운 Instance vs IP 에서는 네트워크 측면을 설명하지 않았습니다. 간단하게 생각해보면 IP 방식은 파드에 직접적으로 연결이 되기 때문에 네트워크 홉을 1칸 덜 사용할 것 같습니다.
실제로 Redit의 Are there advantages to using "target-type: instance" over "target-type: ip" with ALB Ingress?에서도 같은 의견들이 많이 있었습니다.
실제로 테스트를 위해서 tpctraceroute를 이용해서 HTTPS Endpoint에 대한 네트워크 홉 차이를 비교해보겠습니다.
테스트를 위해서 tcptraceroute 모듈을 설치해줍니다.
brew install tcptraceroute
다음과 같이 traceroute를 사용하면 Endpoint 도달까지 사용한 네트워크 홉을 측정할 수 있습니다.
traceroute www.example.com
그 결과를 보면 실제로 Instance 방식이 더 많은 홉을 거치는 것을 알 수 있습니다.