AEWS) EKS Networking

CloudNet@팀에서 진행하는 AWS EKS Workshop 실습 스터디 참가글입니다.

AWS EKS Workshop을 기반으로, AWS EKS 배포 및 실습하고, 내용을 공유합니다.


1. AWS VPC CNI

1.1. Container Network Interface (CNI)

네트워킹은 쿠버네티스의 중심적인 부분이지만, 어떻게 작동하는지 정확하게 이해하기가 어려울 수 있다. 쿠버네티스에는 4가지 대응해야 할 네트워킹 문제가 있다.

  1. 고도로 결합된 컨테이너 간의 통신: 이 문제는 파드와 localhost 통신으로 해결된다.
  2. 파드 간 통신: 이 문제가 이 문서의 주요 초점이다.
  3. 파드와 서비스 간 통신: 이 문제는 서비스에서 다룬다.
  4. 외부와 서비스 간 통신: 이 문제는 서비스에서 다룬다.

네트워크 모델은 각 노드의 컨테이너 런타임에 의해 구현된다. 가장 일반적인 컨테이너 런타임은 컨테이너 네트워크 인터페이스(CNI) 플러그인을 사용하여 네트워크 및 보안 기능을 관리한다. 

클러스터 네트워킹, k8s doc

쿠버네티스는 컨테이너화된 어플리케이션을 호스팅하는 컨테이너 오케스트레이션 플랫폼이다. k8s 이전, 가상화(VM)기반 운영시 호스트 머신 위에 다수의 게스트 OS가 생성되어 운영되었던 것처럼, k8s에서는 소수의 워커 노드위에 다수의 컨테이너 파드들이 생성되어 운영된다.

하나의 머신 위의 상이한 워크로드들이 배포되었다면, 그것들 간의 통신은 어떻게 제어할 수 있을까? 일반적으로는 배포된 각 어플리케이션마다 다른 포트번호를 지정하여 분리할 수 있을것이다. 그러나 생성과 삭제가 자유로운(scale in/out) 컨테이너 환경에서 포트 기반의 운영은 관리와 추적이 쉽지 않다.

쿠버네티스는 Container Network Interface (CNI)를 사용하여 네트워크를 제어한다. CNI는 기본적으로 컨테이너 생성/삭제 시 네트워크에 연결/분리를 수행한다. (링크) 오픈 소스인 CNI 프로젝트를 기반으로, 여러 공급 업체에서는 부가 기능을 덧붙인 CNI 솔루션을 제공한다.

1.2. AWS VPC CNI

AWS의 관리형 쿠버네티스 서비스인 EKS는, 기본적으로 AWS VPC CNI라는 네트워크 플러그인을 사용하도록 구성된다.

Amazon EKS 마이그레이션 요점 정리, 2022 AWS Summit

AWS VPC CNI는 VPC와 긴밀이 통합되어,

  1. 파드가 노드와 같은 대역의 IP를 가져 직접 통신이 가능하고
  2. 라우팅, IAM, 보안그룹과 같은 다른 AWS의 서비스와 간편한 연계

라는 장점이 있다고 생각한다.

Docker Network

새로운 기술/도구/방법론을 공부할 때, 그 대상을 깊이있게 보는것도 중요하지만 ‘이전에는 어떠하였는지 > 어떤식으로 개선되었는지’를 살펴보는것도 이해하는 과정에서 의미가 있다고 생각한다.

쿠버네티스 이전의 컨테이너는 어떻게 운영되었을까?라고 생각했을때, docker-dockercompose/swarm이 떠올랐고 잠시 docker network는 어떤식으로 구성되었는지 살펴보았다. (링크1,링크2)

도커 네트워킹을 살펴 보려면 내용이 깊지만 쿠버 네트워킹을 이해하려는 보조 관점에서 본다면, 기존 네트워크와 VLAN으로 연결된 오버레이 네트워크를 사용한다는 점을 알 수 있다.

각 컨테이너마다 veth(virtual Ethernet devices)가 생성되고, 도커의 네트워크 드라이버와 연결되어 통신이 가능해지게 된다.

Kubernetes Network

일반적인 쿠버네티스의 네트워킹 또한 위의 도커 네트워킹과 유사하게, 컨테이너 별 veth가 생성되고, 오버레이 네트워크를 통해 연결되어 통신이 수행된다는 것을 확인 할 수 있다.

오버레이 네트워크를 통해 통신이 가능해졌지만, 이는 동시에 통신시 오버헤드가 발생한다는 문제점이 있다. 위의 그림에서, Pod A(Container-1)이 Pod D(Container-4)와 통신하기 위해서는 패킷에 목적지인 Inner IP외에도 찾아가기 위한 Outer IP 정보 또한 포함이 되어야한다. 통신시 이를 분석해야하기 때문에 통신이 지연될 수 있는 간접비용(오버헤드)이 된다.

AWS VPC CNI Network

그렇다면 VPC CNI는 어떻게 이를 해결하였는가? 파드 생성시 ENI에 미리 할당된 IP Warm pool에서 사용가능한 IP를 골라, 파드에 보조적인 ENI를 붙여 오버레이가 아닌 일반 VPC 네트워킹을 가능하게 하였다.

파드마다 자신의 ENI를 갖기 때문에,

  1. 기존 노드/파드 IP대역이 달라야했지만 같은 IP대역을 사용할 수 있으며,
  2. 동일대역을 사용하기 때문에 네트워크 통신이 최적화(성능/지연)되었고,
  3. 파드마다 IAM Role, Security Group, 등을 부여할 수 있다.

Pod Networking에 대한 실습은 지난 PKOS2) Kubernetes 네트워크 & 스토리지와 중복되는 부분이 다수 존재한다. 따라서 EKS Workshop 등에서 신규로 추가된 부분 위주로 실습을 진행해보고 정리하고자 한다.

실습환경 배포

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 MyIamUserAccessKeyID=<IAM User의 액세스키> MyIamUserSecretAccessKey=<IAM User의 시크릿 키> ClusterBaseName='<eks 이름>' --region ap-northeast-2

aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=nasirk17 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIAYV... MyIamUserSecretAccessKey='ZTCeoe...' ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용EC2 SSH 접속
ssh -i ~/.ssh/nasirk17.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

2. AWS LoadBalancer Controller

AWS 로드 밸런서 컨트롤러는 Kubernetes 클러스터의 Elastic Load Balancers를 관리하는 데 도움이 되는 컨트롤러입니다.

ALB 를 프로비저닝하여 Kubernetes Ingress 리소스로 사용합니다.

NLB를 프로비저닝하여 Kubernetes Service 리소스로 사용합니다.

이 프로젝트는 이전에 “AWS ALB Ingress Controller”로 알려졌으며 “AWS Load Balancer Controller”로 이름이 변경되었습니다.

AWS Load Balancer Controller is a controller to help manage Elastic Load Balancers for a Kubernetes cluster.

  • It satisfies Kubernetes Ingress resources by provisioning Application Load Balancers.
  • It satisfies Kubernetes Service resources by provisioning Network Load Balancers.

This project was formerly known as “AWS ALB Ingress Controller”, we rebranded it to be “AWS Load Balancer Controller”.

AWS Load Balancer Controller

AWS LoadBalancer Controller는 쿠버네티스에서 Ingress, Service 리소스 생성시, AWS의 ALB,NLB 리소스를 생성하여 연결해주는 컨트롤러이다.

초기버전(v1.x)에서는 AWS ALB Ingress Controller로 명명되어, Ingress-ALB 연동만 가능하였다. v2.x 버전부터 AWS Load Balancer Controller로 개명되어 Ingress-ALB연동과 Service-NLB 연동이 가능하다.

2.1. AWS LoadBalancer Controller 배포 with IRSA

AWS LoadBalancer Controller 설치

# OIDC 확인
aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
aws iam list-open-id-connect-providers | jq

# https://oidc.eks.ap-northeast-2.amazonaws.com/id/03F1CCBC3D2AFA5364D32C63C98E1DF0

# IAM Policy (AWSLoadBalancerControllerIAMPolicy) 생성
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# 혹시 이미 IAM 정책이 있지만 예전 정책일 경우 아래 처럼 최신 업데이트 할 것
# aws iam update-policy ~~~

# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'

# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 >> 자동으로 매칭되는 IAM Role 을 CloudFormation 으로 생성됨!
# IAM 역할 생성. AWS Load Balancer Controller의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할의 이름으로 Kubernetes 서비스 계정에 주석을 답니다
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve

# IRSA 정보 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME

# 서비스 어카운트 확인
kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh

# Helm Chart 설치
helm repo add eks https://aws.github.io/eks-charts
helm repo update
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

설치 확인

## 설치 확인
kubectl get crd
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'

#  Service Account:  aws-load-balancer-controller
 
# 클러스터롤, 롤 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role

2.2. 서비스/파드 배포 테스트 with NLB

kubernetes service에 NLB를 부여하기 위해서는, spec.loadBalancerClass과 사용할 설정의 annotations을 입력하여 생성할 수 있다. (링크)

NLB 배포 테스트 하기

# 모니터링
watch -d kubectl get pod,svc,ep

# 작업용 EC2 - 디플로이먼트 & 서비스 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
cat echo-service-nlb.yaml | yh
kubectl apply -f echo-service-nlb.yaml

# 확인
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq

# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text

# provisioning


# 웹 접속 주소 확인
kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'

# Pod Web URL = http://k8s-default-svcnlbip-48ff1633c1-7e96caaf5304c33b.elb.ap-northeast-2.amazonaws.com

결과 확인

# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f

# 분산 접속 확인
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
curl -s $NLB
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
  52 Hostname: deploy-echo-55456fc798-2w65p
  48 Hostname: deploy-echo-55456fc798-cxl7z

# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done

  • 분산 접속 확인 결과
  • 지속적인 접속 시도 결과

2.3. 서비스/파드 배포 테스트 with ALB

kubernetes ingress에 ALB를 부여하기 위해서는, Ingress 리소스를 생성하여야한다. Ingress의 설정값으로 spec.ingressClassName과 사용할 설정의 annotations을 입력하여 생성할 수 있다. (링크)

ALB 배포 테스트 하기

# 게임 파드와 Service, Ingress 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
cat ingress1.yaml | yh
kubectl apply -f ingress1.yaml

# 모니터링
watch -d kubectl get pod,ingress,svc,ep -n game-2048

# 생성 확인
kubectl get-all -n game-2048
kubectl get ingress,svc,ep,pod -n game-2048
kubectl get targetgroupbindings -n game-2048
# NAME                               SERVICE-NAME   SERVICE-PORT   TARGET-TYPE   # AGE
# k8s-game2048-service2-31ed711124   service-2048   80             ip       
# 72s

# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048

# 게임 접속 : ALB 주소로 웹 접속
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'
# Game URL = http://k8s-game2048-ingress2-70d50ce3fd-668900855.ap-northeast-2.elb.amazonaws.com

결과 확인

3. ExternalDNS

ExternalDNS는 외부로 노출된 Kubernetes Service 및 Ingress를 DNS 제공자와 동기화합니다.

ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.

ExternalDNS Github

ExternalDNS는 Service/Ingress 생성 시, 도메인을 설정해두면 각 CSP의 DNS 서비스에 도메인과 리소스를 연결해주는 레코드(DNS-A / 설명-TXT)를 생성하는 컨트롤러이다.

ExternalDNS는 쿠버네티스 내에 존재하는 컨트롤러이다. 클러스터 내의 구성 요소가 클러스터 외부의 리소스를 조작하려면 적절한 권한이 부여되어야한다. 이러한 권한 부여는 일반적으로 아래의 3가지 방식으로 부여할 수 있다.

  1. IRSA (IAM Role for Service Account)
  2. Node IAM Role
  3. Static Credential (Access Key)

리소스 조작을 수행하는 쿠버네티스 구성 요소에 적절한 권한(IRSA)이 부여되어있지 않으면, 구성요소가 있는 Node의 IAM Role 권한에 따라서 조작을 수행한다. 2. LBC는 IRSA를 구성하였고, 이번 ExternalDNS에는 IRSA 구성 대신 필요 권한이 부여된 Node IAM Role에 따라 Route53 레코드 조작을 수행한다.

AWS Route 53 정보 확인 & 변수 지정 (도메인 소유 필요)

# 자신의 도메인 변수 지정 : 소유하고 있는 자신의 도메인을 입력하시면 됩니다
MyDomain=<자신의 도메인>
MyDomain=devxmonitor.click

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." | jq
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Name"
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text
MyDnzHostedZoneId=`aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text`
echo $MyDnzHostedZoneId

# (옵션) NS 레코드 타입 첫번째 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
# (옵션) A 레코드 타입 모두 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']"

# A 레코드 타입 조회
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" --output text

# A 레코드 값 반복 조회
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done

ExternalDNS 설치

# EKS 배포 시 Node IAM Role 설정되어 있음
# eksctl create cluster ... --external-dns-access ...

# 
MyDomain=<자신의 도메인>
MyDomain=devxmonitor.click

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)

# 변수 확인
echo $MyDomain, $MyDnzHostedZoneId

# ExternalDNS 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
cat externaldns.yaml | yh
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# 확인 및 로그 모니터링
kubectl get pod -l app.kubernetes.io/name=external-dns -n kube-system
kubectl logs deploy/external-dns -n kube-system -f

Service(NLB) + 도메인 연동(ExternalDNS)

# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
kubectl logs deploy/external-dns -n kube-system -f

# 테트리스 디플로이먼트 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:

    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  name: tetris
  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-cross-zone-load-balancing-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
    #service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
  selector:
    app: tetris
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
EOF

# 배포 확인 : CLB 배포 확인
kubectl get deploy,svc,ep tetris

# NLB에 ExternanDNS 로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"

# Route53에 A레코드 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq .[]

# 확인
dig +short tetris.$MyDomain @8.8.8.8
dig +short tetris.$MyDomain

# 도메인 체크
echo -e "My Domain Checker = https://www.whatsmydns.net/#A/tetris.$MyDomain"

# 웹 접속 주소 확인 및 접속
echo -e "Tetris Game URL = http://tetris.$MyDomain"
# Tetris Game URL = http://tetris.devxmonitor.click

실습 확인

  • externalDNS log
# externalDNS 로그 조회
k logs -n kube-system external-dns-78dc4b5954-mh6qm

# time="2023-05-06T19:39:56Z" level=info msg="Desired change: CREATE cname-tetris.devxmonitor.click TXT [Id: /hostedzone/Z0036594N9P48ZNJWFLY]"
# time="2023-05-06T19:39:56Z" level=info msg="Desired change: CREATE tetris.devxmonitor.click A [Id: /hostedzone/Z0036594N9P48ZNJWFLY]"
# time="2023-05-06T19:39:56Z" level=info msg="Desired change: CREATE tetris.devxmonitor.click TXT [Id: /hostedzone/Z0036594N9P48ZNJWFLY]"
# time="2023-05-06T19:39:56Z" level=info msg="3 record(s) in zone devxmonitor.click. [Id: /hostedzone/Z0036594N9P48ZNJWFLY] were successfully updated"
  • Route 53 조회
  • DNS 접속 확인


4. 도전 과제

4.1. Multiple Ingress Pattern

Ingress를 활용하여 서비스를 노출하다보면 필연적으로 ALB가 많아지게 된다. 기본적으로 1개의 Ingress마다 1개의 ALB가 할당되기 때문이다.

이를 해결하기 위해 1.Ingress에 alb.ingress.kubernetes.io/group.name Annotation을 할당하거나, 2. Path 기반 라우팅(Fanout)을 설정하여, 여러 Ingress를 하나의 ALB로 묶을 수 있다.

EKS Workshop에서는 Annotation 방식을 활용한 예시가 소개되어있다. 이를 앞서 배포하였던 2048과 tetris 서비스에 적용한다면 다음과 같다

ns 생성 후 기본 deploy/svc 배포

  • 새로운 Namespace(game) 생성 후, 2048/tetris 각각 deployment/service 배포
# namespace,deployment,service를 group.yaml로 저장
cat <<EOF >>group.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: game
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game
  name: game-2048
  labels:
    app: game-2048
spec:
  selector:
    matchLabels:
      app: game-2048
  replicas: 2
  template:
    metadata:
      labels:
        app: game-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: game-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: game
  name: game-2048
  labels:
    app: game-2048
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: game
  name: tetris
  labels:
    app: tetris
spec:
  selector:
    app: tetris
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: ClusterIP
EOF

# 리소스 생성
kubectl apply -f group.yaml

2048,tetris 각 서비스에 대한 ingress 생성

  • 각 서비스에 대한 ingress 생성
# 각 서비스에 대한 ingress 생성 후 ingress.yaml로 저장
# annotation의 group.name에 various-game 이라는 그룹명 지정
cat <<EOF >>ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game
  name: ingress-tetris
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/group.name: various-game
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        - path: /tetris
          pathType: Prefix
          backend:
            service:
              name: tetris
              port:
                number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game
  name: ingress-2048
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/group.name: various-game
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: game-2048
              port:
                number: 80
EOF

# ingress 생성
kubectl apply -f ingress.yaml

생성 결과 확인

  • 터미널에서, k get ing
  • 두개의 ingress가 동일한 ALB주소 (address)를 갖는것을 확인 가능
  • 콘솔에서, 로드밸런서의 리스너 규칙 조회
  • 기본적으로 2048로 라우팅되며, /tetris를 붙일시 tetris로 이동


<ALB주소>/tetris로 접속시 404 에러 해결

그러나 실제로 /tetris 접근 시 404 Not Found로 접속되지 않는 문제가 있다.

아래의 해결방안은 아마도 같은 스터디를 하고 계신..? pkos) aws ingress controller 체험기글을 보고 정리하였다. 다시한번 감사를!

이를 해결하기 위해서, tetris pod의 로그를 보면 다음과 같다

# 로그 조회
k logs tetris-855bfd84b5-hr49b 

...
2023/05/06 22:52:56 [error] 31#31: *4 open() "/usr/share/nginx/html/tetris" failed (2: No such file or directory), client: 192.168.3.62, server: localhost, request: "GET /tetris HTTP/1.1", host: "k8s-game-ingresst-04b23eed57-145348933.ap-northeast-2.elb.amazonaws.com"
2023/05/06 22:52:57 [error] 31#31: *4 open() "/usr/share/nginx/html/tetris" failed (2: No such file or directory), client: 192.168.3.62, server: localhost, request: "GET /tetris HTTP/1.1", host: "k8s-game-ingresst-04b23eed57-145348933.ap-northeast-2.elb.amazonaws.com"
...

tetris 이미지에서는 기본적으로 /usr/share/nginx/html 경로의 index.html을 조회해서 페이지가 제공되는데, 경로를 추가시켜 존재하지않는 디렉토리인 /usr/share/nginx/html/tetris에서 요청을 하기때문에 발생하는 에러이다. 따라서 해당 경로로 파일들을 옮겨주여야한다.

# 디렉토리 생성 후 소스 복사
mkdir -p /usr/share/nginx/html/tetris && cp -r /usr/share/nginx/html/* /usr/share/nginx/html/tetris/


리스너 규칙간 우선순위 조정

  • /tetris의 순위가 /*보다 먼저 올라와있어야 404로 안빠지고 잘 접속되는듯함
  • 결과

4.2. How To Expose Multiple Applications on Amazon EKS Using a Single Application Load Balancer

동명의 AWS 블로그 글에서는, Path 기반 라우팅이 적용된 Fanout의 예시가 소개된다.

ingress-fanout-diagram

4.1.과는 달리, annotation의 groupname이 제거되고, 각각 path별 백엔드 서비스가 설정되어있다.

cat <<EOF >> color-app.yaml 
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: color-app-ingress
  namespace: color-app
  labels:
    app: color-app
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    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:
  rules:
    - http:
        paths:
          - path: /yellow
            pathType: Prefix
            backend:
              service:
                name: yellow-service
                port:
                  number: 80                        
          - path: /green
            pathType: Prefix
            backend:
              service:
                name: green-service
                port:
                  number: 80
EOF

이를 2048과 tetris에 적용하면 다음과 같다

Fanout example

  • namespace,deployment,service,ingress 생성 후 배포
# 구성 정보를 fanout.yaml에 저장
cat <<EOF >>fanout.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: fanout
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: fanout
  name: game-2048
  labels:
    app: game-2048
spec:
  selector:
    matchLabels:
      app: game-2048
  replicas: 1
  template:
    metadata:
      labels:
        app: game-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: game-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: fanout
  name: game-2048
  labels:
    app: game-2048
spec:
  ports:
    - port: 8001
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: fanout
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  namespace: fanout
  name: tetris
  labels:
    app: tetris
spec:
  selector:
    app: tetris
  ports:
  - port: 8002
    protocol: TCP
    targetPort: 80
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: fanout
  name: test-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /2048
        pathType: Prefix
        backend:
          service:
            name: game-2048
            port:
              number: 8001
      - path: /tetris
        pathType: Prefix
        backend:
          service:
            name: tetris
            port:
              number: 8002
EOF

# 리소스 생성
kubectl apply -f fanout.yaml

# ingress 경로 확인
kubectl get ing -n fanout

# k8s-fanout-testingr-080ee813ac-989044432.ap-northeast-2.elb.amazonaws.com

각 pod별 nginx 설정 변경

  • 2048 파드 내 수정
# 2048 파드 접속
# bash이 없어서, 기본 sh로 접속

k exec <2048 파드명> -it -- sh
mkdir -p /usr/share/nginx/html/2048 && cp -r /usr/share/nginx/html/* /usr/share/nginx/html/2048/
  • tetris 파드 내 수정
# tetris 파드 접속

k exec <tetris파드명> -it -- /bin/bash
mkdir -p /usr/share/nginx/html/tetris && cp -r /usr/share/nginx/html/* /usr/share/nginx/html/tetris/
  • 결과 확인
  • 언제 무엇을 사용해야할까?

다수의 Ingress를 하나의 ALB로 묶기 위해, annotation의 groupname을 사용하는 방식과 paths의 path를 사용하는 방식을 알아보았다.

실습해보면서 든 생각은, 이미 ingress가 생성되어있고, 빠르게 정리를 해야한다면 annotation만 붙이면 되는 방식이 간단하게 적용할 수 있을 듯하고, 조금 더 많은 설정이 필요하다면 path로 붙이는게 적절하지 않을까 하는 생각을 했다.

4.3. SSL+ExternalDNS 적용하기

마지막으로, 지금까지의 실습에 HTTPS를 적용하여 보안성을 확보하고, DNS를 붙여 기억하기 쉬운 웹 주소를 붙여보겠다. 다만 이를 위해선 사전에 구매한 도메인주소가 필요하다.

구성 파일

  • namespace,deployment,service,ingress 생성 후 배포
  • ingress 설정 중, CERT_ARN은 자신의 인증서로 수정
  • ingress 설정 중, spec.rules.host의 값을 자신의 도메인으로 수정
# 구성 정보를 final.yaml에 저장
cat <<EOF >>final.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: games
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: games
  name: game-2048
  labels:
    app: game-2048
spec:
  selector:
    matchLabels:
      app: game-2048
  replicas: 1
  template:
    metadata:
      labels:
        app: game-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: game-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: games
  name: game-2048
  labels:
    app: game-2048
spec:
  ports:
    - port: 8001
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: games
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  namespace: games
  name: tetris
  labels:
    app: tetris
spec:
  selector:
    app: tetris
  ports:
  - port: 8002
    protocol: TCP
    targetPort: 80
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: games
  name: test-ingress
  annotations:
    alb.ingress.kubernetes.io/certificate-arn: CERT_ARN
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/healthcheck-port: traffic-port
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
  ingressClassName: alb
  rules:
  - host: games.devxmonitor.click
    http:
      paths:
      - path: /2048
        pathType: Prefix
        backend:
          service:
            name: game-2048
            port:
              number: 8001
      - path: /tetris
        pathType: Prefix
        backend:
          service:
            name: tetris
            port:
              number: 8002
EOF

# 리소스 생성
kubectl apply -f final.yaml

각 pod별 nginx 설정 변경

  • 2048 파드 내 수정
# 2048 파드 접속
# bash이 없어서, 기본 sh로 접속

k exec <2048 파드명> -it -- sh
mkdir -p /usr/share/nginx/html/2048 && cp -r /usr/share/nginx/html/* /usr/share/nginx/html/2048/
  • tetris 파드 내 수정
# tetris 파드 접속

k exec <tetris파드명> -it -- /bin/bash
mkdir -p /usr/share/nginx/html/tetris && cp -r /usr/share/nginx/html/* /usr/share/nginx/html/tetris/
  • 웹 접속 확인

Leave a Reply