5주차 - AWS Ingress 딥다이브

월간-CS | 쿠버네티스 비기너 클래스
이민석's avatar
Aug 27, 2024
5주차 - AWS Ingress 딥다이브

지금까지

아래 과정을 수행하였습니다.

  1. 1주차 - Docker & EKS 기초

  2. 2주차 - 다양한 유형의 앱 배포하기

  3. 3주차 - Pod 그리고 RDS 연결

  4. 4주차 - CLB, ALB 그리고 aws-load-balancer-controller

이번 주차에는

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

  1. ALB Ingress - SSL 준비하기

  2. ALB Ingress - SSL Annotation

  3. ALB Ingress - DNS A Record

ALB Ingress - SSL 준비하기

위에서 배운 방식으로 생성한 AWS ELB는 HTTP Protocol을 사용합니다.
이때 HTTPS Protocol을 사용하기 위해서 SSL 관련 사전 준비가 필요합니다.

  1. AWS Route53 DNS 도메인 등록하기

  2. AWS Certificate Manager로 SSL Certificate 발급받기

  3. Ingress에 SSL Annotation 추가하기

  4. 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

더 알아보기

기존에 알고 있던 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 방식이 더 많은 홉을 거치는 것을 알 수 있습니다.

Share article

Unchaptered