본문으로 건너뛰기

험난한 Kubernetes 전환기 (1) - Azure Kubernetes Service

· 약 11분
Austin Lee
DevOps Engineer @ Allganize

AKS Icon

오랜만에 작성하는 블로그입니다.
업무상으로 외부에 많이 나가면서 순수하게 시간이 부족하기도 했고, 참여한 작업들의 규모가 생각보다 커서 결론이 나는 데 꽤 걸렸던 것 같네요.

다른 일들은 기회가 되면 나중에 적어 보는 것으로 하고,
최근 중요하게 진행하고 있는 작업은 사내 배포 환경을 Kubernetes로 전환하는 것입니다.
이 글을 시작으로 Kubernetes 전환 과정에서 겪은 어려움과 해결 방법들을 정리해 보려고 합니다.

1번 타자, Azure

현재 회사에서는 AWS와 Azure를 모두 사용하고 있습니다.
물론 클라우드 시장에서는 AWS가 점유율이 가장 높지만, Azure는 일본에서 매우 높은 점유율과 신뢰도를 보유하고 있고 Azure 환경에 구성하는 것이 요구사항에 포함되는 경우도 있습니다. 일본 쪽 고객이 상당히 많기 때문에 일본 쪽의 인프라 문제를 우선적으로 개선하는 것이 매우 중요했습니다.

그래서 첫 번째 Kubernetes 환경은 AKS(Azure Kubernetes Service)를 활용하여 구축하게 되었습니다.

NAT Gateway

Kubernetes 환경을 구축할 때 가장 중요했던 것은 네트워크 설정이었습니다.
그중에서도 NAT Gateway를 우선 구성할 필요가 있었습니다.

NAT Gateway는 Kubernetes 클러스터와 인터넷의 중간에 위치해서 불필요한 트래픽을 차단하고 보안을 강화하는 데 도움이 됩니다.
기본적으로 아웃바운드 트래픽, 즉 나가는 트래픽과 그 응답으로 돌아오는 트래픽만 NAT Gateway를 통과할 수 있습니다.

조금 더 현실적인 이유로는, 일본의 상당수 고객들이 외부 서비스 사용 시 특정 IP 대역만 방화벽을 개방하기 때문에 가급적 변경되지 않는 IP 대역을 두고, 변경이 필요할 경우 이를 사전에 안내해야 하기 때문입니다. 그래서 NAT Gateway를 구성해 두면 Kubernetes 클러스터에 변경이 생기더라도 고정된 주소로 서비스를 제공할 수 있다는 점 역시 큰 이유였습니다.

NAT Gateway는 Stack Overflow공식 문서를 참고하여 구성하였습니다.

참고로, 별도 설정을 하지 않는다면 NAT Gateway는 Zone-redundant, 즉 특정 Availability zone에 종속되지 않는 형태로 분산되어 구성됩니다.
저희는 해당 설정이 의도와 맞았기 때문에 별도 조치를 하지 않았는데, 특정 리전을 지정해야 한다면 설정을 추가해야 합니다.

Azure Load Balancer와 Azure Application Gateway

Azure Load Balancer는 구축 과정에서 가장 문제를 많이 일으킨 부분이었습니다.
기존 AWS 환경에서는 Kubernetes 클러스터에 NGINX Ingress Controller를 ALB(Application Load Balancer)로 연결하여 사용하고 있었고, 별도의 설정 변경을 하지 않아도 큰 문제가 없었습니다.

처음에는 Azure에도 비슷하게 NGINX Ingress Controller를 구성하고, Annotation 정도를 추가하여 Azure Load Balancer를 사용하면 된다고 생각했습니다.
하지만, 예상과 달리 많은 문제가 있었습니다.

  1. Azure Load Balancer는 인증서를 Load balancer에 할당할 수 없습니다. AWS에는 AWS Certificate Manager에서 생성한 인증서를 ALB에 할당할 수 있고, 인증서의 자동 갱신도 지원합니다.
    하지만 초기 구성에서는 인증서 관련 설정을 할 수 없었습니다.
  2. Azure Load Balancer는 네트워크 관련 많은 제약이 있습니다.
    기본적으로 L4 Load balancer에 해당하고, 방화벽과 요청 헤더 변경 등을 지원하지 않았습니다.

나중의 편의성도 고려하고, 실제로 이로 인해 불편함이 생길 가능성도 있었기 때문에 저희는 Azure에서 제공하는 L7 Load balancer인 Azure Application Gateway를 사용하기로 결정하였습니다.

추가로 현재 인프라를 고려했을 때
AWS에서도 가급적 L4 Load balancer인 NLB(Network Load Balancer)보다 L7 Load balancer인 ALB를 사용해야겠다는 교훈도 얻었습니다.

이렇게 설정을 마쳤지만 배포 과정에서 1가지 추가적인 문제가 발생했습니다. 원인은 크게 2가지였습니다.

  1. Azure의 AGIC(Application Gateway Ingress Controller)는 별도 설정이 없다면
    X-Forwarded-For 헤더를 IP:Port 형식으로 변경합니다.
  2. 백엔드에서 이 헤더 값을 읽어 작업하는 코드가 있었고, 해당 형식을 고려하지 않아 문제가 발생했습니다.

포트 정보의 경우 공식 문서에서 제거하는 방법이 나와 있어 조치할 수 있었고, 그 외에도 팀원들과 함께 필요한 헤더와 백엔드에서 사용하는 헤더를 조사하여 몇 가지 추가 조정을 수행하였습니다.

노드 Auto Scaling

마지막으로 중요했던 작업 중 하나는 노드의 Auto Scaling이 가능하도록 설정하는 작업이었습니다. Auto Scaling은 사용 자원을 상황에 맞게 조절하여, 유동성을 확보할 수 있고 비용 효율도 높일 수 있습니다. 특히나, 일본 리전의 경우 업무 시간을 중심으로 특정 피크 타임에 트래픽이 몰리는 경향이 있었고 기존에 이로 인한 문제도 있었기 때문에 꼭 필요한 작업이었습니다.

Azure에서는 이를 위해 Cluster autoscaler를 제공하고 있습니다.
Terraform에서도 AKS 구성에 옵션을 추가하면 비교적 간편하게 구성할 수 있었습니다.
Karpenter를 사용할 수 있는 선택지도 있었지만 Azure 환경에서는 조건이 제한적이어서 사용하지 않았습니다.

1가지 알아둘 점은, Cluster autoscaler에서 노드 생성 이벤트가 발생하는 시점은 리소스 부족으로 인해 Pod가 스케줄링이 되지 않을 때라는 점입니다. 노드가 줄어들 때는 리소스 사용률을 고려합니다.
이 메커니즘이 구성 과정에 영향을 미치지는 않아 설정 변경은 없었습니다.

테스트는 1차적으로 Stress 이미지를 여러 개 배포하는 식으로 부하를 주어 진행하였고, 이후 실제 리소스를 배포해 보면서 검증을 거쳤습니다.
아래는 실제 사용한 YAML 파일입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
generation: 2
labels:
app: stress
name: stress
spec:
progressDeadlineSeconds: 600
# replicas: 10
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: stress
template:
metadata:
labels:
app: stress
spec:
containers:
- image: vish/stress
imagePullPolicy: Always
name: stress
resources:
limits:
cpu: 2000m
memory: 8Gi
requests:
cpu: 2000m
memory: 8Gi
args:
- -cpus
- "2"
- -mem-alloc-size
- "1Gi"
- -mem-alloc-sleep
- "1000ms"
- -mem-total
- "7Gi"
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

마치며

Azure에서 Kubernetes 환경을 구축하며 겪은 큰 문제들을 정리해 보았습니다.
이외에도 여러 문제들이 있었지만, 다 나열하기는 힘들고 공개할 수 없는 내용들도 있어 개인적으로 정리할 예정입니다.

Azure는 확실히 참고할 자료가 상대적으로 적습니다. 이로 인해 Azure Private Link처럼 유용해 보이지만 초기 작업 계획과 안정성 등을 고려해 도입을 미룬 것도 있었습니다.
결국 계속 다루어야 하는 환경이기도 하고, 이렇게 몇 가지 생각해 둔 것들은 천천히 검증을 거쳐 도입하려 합니다.

일본 지역을 시작으로 다른 환경도 Kubernetes로 전환하고, 점진적으로 트래픽을 전환하여 최종적으로는 기존의 Docker Compose 기반 환경을 완전히 없애는 것이 목표입니다.
앞으로 더 많은 문제들이 있을 것이라고 생각하는데, 꼭 해 보고 싶었던 작업이기 때문에 가능한 데까지 노력해 보고 싶네요.

참고 자료