IT/DevOps

패킷 덤프를 통해 확인하는 ALB와 NLB의 차이점 (2) - NLB 동작 원리

Aaron's papa 2021. 8. 8. 16:55
반응형

지난번 글에서는 패킷 덤프를 통해 ALB의 동작 원리에 대해 살펴봤습니다. 이번 글에서는 패킷 덤프를 통해 살펴보면서 NLB는 ALB와 어떻게 다르게 동작하는지 그 동작 원리에 대해서 살펴보겠습니다.


NLB 생성하기

NLB도 ALB와 똑같이 80 포트로 받아서 8080 포트로 넘겨주도록 만들어 보겠습니다. 이번에도 테라폼을 이용해서 만들어 보겠습니다.

NLB를 만들 때는 SecurityGroup을 지정하지 않는다는 것을 눈여겨 봐주시기 바랍니다.

resource "aws_lb" "nlb" {
  name                             = "hello-jib-nlb"
  subnets                          = ["subnet-182c2954", "subnet-28fe2f43", "subnet-bb0230e7", "subnet-c2bf3db9"]
  internal                         = false
  load_balancer_type               = "network"
  ip_address_type                  = "ipv4"
  enable_cross_zone_load_balancing = true
}

resource "aws_lb_target_group" "nlb" {
  name                 = "hello-jib-nlb"
  port                 = 8080
  protocol             = "TCP"
  vpc_id               = "vpc-84ce69ef"
  slow_start           = 0
  deregistration_delay = 0
}

resource "aws_lb_listener" "nlb" {
  load_balancer_arn = aws_lb.nlb.arn
  port              = "80"
  protocol          = "TCP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.nlb.arn
  }
}

resource "aws_lb_target_group_attachment" "hello-jib-nlb" {
  target_group_arn = aws_lb_target_group.nlb.arn
  target_id        = "i-0c471083e79966606"
}

enable_cross_zone_load_balancingtrue로 설정한 것도 눈여겨보시기 바랍니다. NLB는 생성할 때 설정한 서브넷 별로 하나씩 생성됩니다. 그리고 자신이 속해 있는 서브넷의 AZ에 있는 백엔드로만 트래픽을 전달합니다. 그래서 지금처럼 백엔드 서버로 사용할 EC2 서버가 한 대만 있는 경우에는 다른 AZ에 있는 NLB로 요청이 갔을 경우 트래픽이 전달되지 않을 수 있습니다.

enable_cross_zone_load_balancing이 false일 경우

그래서 꼭 enable_cross_zone_load_balancing 옵션을 true로 설정해 줘야 합니다.

enable_cross_zone_load_balancing이 true일 경우

terraform apply 를 통해 프로비저닝이 성공하고 나면 curl 명령으로 확인해 보겠습니다.

❯ curl http://hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com
curl: (7) Failed to connect to hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com port 80: Operation timed out

하지만 뭔가 이상합니다. 접속이 안될 겁니다. 왜 그럴까요? 우선 텔넷 명령을 통해 네트워크 연결은 잘 되는지 확인해 보겠습니다.

❯ telnet hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com 80
Trying 3.35.60.43...
telnet: connect to address 3.35.60.43: Operation timed out
Trying 15.164.4.153...
telnet: connect to address 15.164.4.153: Operation timed out
Trying 3.38.26.192...
telnet: connect to address 3.38.26.192: Operation timed out
Trying 15.165.198.57...
telnet: connect to address 15.165.198.57: Operation timed out
telnet: Unable to connect to remote host

80 포트로 텔넷으로 연결이 되지 않는 것을 보면 네트워크 통신 자체가 되지 않는다는 것을 알 수 있습니다. NLB를 사용하면 바로 이런 부분이 가장 헷갈리는데요, ALB처럼 SecurityGroup이 있는 것도 아닌데 어떻게 포트를 열어 줄 수 있을까요? 그 답은 백엔드에 적용되는 SecurityGroup에 있습니다.


SecurityGroup 수정하기

NLB는 ALB와는 다르게 중간에서 프록싱을 해주지 않습니다. 그래서 백엔드에 있는 서버에 연결된 SecurityGroup의 영향을 받는데요, 우리가 앞에서 EC2에 적용한 SecurityGroup에는 8080 포트에 대해서 ALB만 접근할 수 있게 열어 줬기 때문에 붙을 수가 없는 겁니다. 여기서 또 의문이 하나 생기는데요, 그럼 80 포트를 열어야 할까요 8080 포트를 열어야 할까요? 우리가 NLB에서 열어준 포트는 80 포트이니 80 포트를 열어야 할까요? 하지만 백엔드 애플리케이션에서 열어야 할 포트는 8080 포트인데요, 어떤 포트를 열어야 할까요? 정답은 8080 포트입니다. 일단 8080 포트를 열어보고 더 자세한 이야기를 나눠 보겠습니다. SecurityGroup과 관련된 테라폼 코드를 아래와 같이 수정해 줍니다.

resource "aws_security_group" "ec2" {
  name        = "hello-jib-ec2"
  description = "hello-jib-ec2"
  vpc_id      = "vpc-84ce69ef"

  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["1.240.235.98/32"]
  }

  ingress {
    from_port = 8080
    to_port   = 8080
    protocol  = "tcp"

    security_groups = [
      aws_security_group.alb.id
    ]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

8080 포트를 열어 주고 curl 명령으로 테스트해보면 이제 접근이 될 겁니다.

❯ curl http://hello-jib-nlb-612cb6631d9b54fa.elb.ap-northeast-2.amazonaws.com
hello, jib!

그리고 telnet 을 이용한 80 포트 연결도 잘 되는 것을 알 수 있습니다.

❯ telnet hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com 80
Trying 15.165.198.57...
Connected to hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com.
Escape character is '^]'.
^]
telnet> quit
Connection closed

너무 신기하지 않나요? 우리가 열어준 포트는 8080 포트인데 NLB에서 80 포트가 접근이 된다니, 어떻게 이런 일이 가능한 걸까요? 이걸 이해하기 위해 패킷 덤프를 한 번 떠보겠습니다.

NLB에서의 패킷 덤프

ALB에서의 패킷 덤프와 비교했을 때 다른 부분이 보이시나요? 바로 SourceIP 입니다. SourceIP가 로컬에서 사용하는 제 노트북의 IP가 그대로 찍혀 있습니다. 그리고 포트도 8080 포트인 것으로 보이고요. 요청했을 때는 NLB로 80 포트로 접근했는데 서버에서 덤프를 떠보니 저의 소스 IP에 포트도 8080으로 찍혀 있습니다. 이 패킷들의 내용도 살펴보겠습니다.

NLB 패킷 내용

ALB에 비해 패킷도 굉장히 단순합니다. 요청자가 요청할 때 생성한 헤더들만 들어있고 다른 헤더들은 추가되어 있지 않습니다. 바로 이 부분이 NLB가 ALB와 가장 크게 다른 점입니다. NLB는 ALB와는 다르게 프록싱을 하지 않고 요청 패킷을 그대로 백엔드로 보냅니다. 즉 앞단에서는 80 포트로 요청이 들어간 것처럼 보이지만 뒷단에서는 이 포트를 8080 포트로 바꿔 버리는 거죠.

일종의 패킷 포워더처럼 동작하게 되는 겁니다.

만약 NLB에서 설정한 포트와 타겟 그룹의 포트가 같다면, 즉 NLB에서도 우리가 8080 포트로 열어 줬다면 사실 포트를 바꾸는 작업조차도 불필요하게 되죠. NLB는 자신이 받은 패킷을 그대로 백엔드로 전달하게 됩니다. 바로 이게 NLB가 L4에서 동작한다는 것을 의미합니다. ALB처럼 클라이언트 요청 패킷의 헤더를 보거나 별도의 헤더를 추가하거나 하는 작업을 하지 않습니다. 응용 프로토콜의 헤더는 L7의 영역이기 때문이죠. L4은 TCP/UDP 의 영역이기 때문에 그저 받은 패킷의 포트를 수정하는 정도 밖에 하지 않습니다. 그래서 ALB에 비해 더 성능이 좋고 더 많은 트래픽을 처리할 수 있습니다.

NLB의 동작 원리

하지만 이렇게 동작하는 NLB도 딱 한 번 클라이언트와의 직접 통신을 중개하는 역할을 하게 되는데요, 바로 TLS 기능을 사용할 때입니다.


NLB에서의 TLS 처리

TLS는 Secure 소켓 통신을 의미 합니다. HTTP 프로토콜에 TLS를 적용하면 HTTPS가 되는 것처럼 NLB에서 열어준 TCP 포트에 대해 TLS를 적용하면 Secure TCP 통신이 가능해 집니다.

HTTP 외에 다른 프로토콜의 경우 TLS 활성화가 지원 되어야 사용할 수 있습니다. TLS를 활성화 하기 전에 해당 프로토콜이 TLS를 지원하는지 확인해 봐야 합니다.

NLB에서도 ALB처럼 TLS를 사용할 수 있고 TLS에 대한 처리를 NLB에서 할 수 있습니다. ALB처럼 TLS에 대한 핸드쉐이킹을 NLB에서 지원하기 때문에 백엔드에 있는 서버들에 인증서를 설치하고 관리해야 하는 업무에서 해방될 수 있습니다. 이건 꽤 중요하고 편한 기능입니다. 수십, 수백 대의 서버가 있을 때는 인증서를 설치하고 관리하는 업무도 굉장히 큰 이슈가 될 수 있기 때문입니다. (보통 인증서의 주기를 2년 단위로 발행하고 관리하는데.. 2년이 생각보다 빨리 옵니다.)

NLB에서 TLS를 처리하게 되면 TLS에 대한 핸드쉐이킹 작업을 NLB에서 처리해야 하기 때문에 NLB가 클라이언트와의 관계에 직접으로 개입해서 TLS 핸드쉐이킹을 진행합니다. 다른 포트들에 대해서는 백엔드 서버로 포트를 넘겨주는 작업만 하는 것에 비하면 TLS 처리에 대해서만은 특별 대우를 하게 되는 거죠. 그리고 TLS에 대한 핸드쉐이크 작업이 완료된 후에 백엔드 서버로 패킷을 포워딩하기 때문에 443 포트로 텔넷은 되는데 curl로 요청하면 응답을 제대로 받지 못하는 일이 생깁니다.

80 포트와는 다르게 443 포트의 연결 처리를 NLB가 하기 때문에 텔넷은 되지만 443 포트 연결 처리 후의 GET 요청 같은 HTTP 요청을 백엔드 서버로 넘기지 못하기 때문에 curl을 사용했을 때 아래와 같은 에러가 발생할 수 있습니다.
❯ curl https://hello-jib-nlb-5c09b4432ba05ce7.elb.ap-northeast-2.amazonaws.com
curl: (52) Empty reply from server

443으로 텔넷 연결은 가능한데, 위와 같이 응답을 못 받아온다면 백엔드 서버의 애플리케이션이 사용하는 포트에 대한 SecurityGroup 규칙을 확인해 보시면 됩니다.


마치며

지금까지 살펴본 내용들을 바탕으로 확인한 두 LB의 결정적인 차이는 아래와 같습니다.

 

첫 번째, ALB는 사용자의 HTTP/HTTPS 요청 패킷의 헤더까지 살펴볼 수 있으며 헤더를 추가하거나 헤더 값을 바탕으로 다르게 동작하게 할 수 있습니다. 반대로 NLB는 사용자의 TCP/UDP 패킷까지만 살펴볼 수 있으며 패킷의 목적지 포트를 수정하는 형태로 동작하게 할 수 있습니다.

 

두 번째, ALB는 자체 SecurityGroup을 가지고 있어서 이를 바탕으로 포트에 대한 접근을 통제합니다. 반면 NLB는 자체 SecurityGroup을 가지고 있지 않으며 백엔드 서버에 연결된 SecurityGroup에 의해 포트에 대한 접근이 통제됩니다.

 

따라서 HTTP/HTTPS를 사용하고 있으며 요청 헤더를 기반으로 부하 분산을 해야 하는 애플리케이션의 경우 혹은 AWS WAF와 같은 L7 기반의 방화벽을 사용해야 하는 경우에는 ALB를 사용하고, 그 외의 경우라면 NLB를 사용하는 것이 좋습니다. 예를 들면 카프카 중개인들의 부하 분산을 위한 LB 같은 게 필요하다면 NLB를 사용할 수 있겠죠. 

요즘에는 AWS WAF 사용의 경우 선택이 아니라 필수로 여겨지고 있기 때문에 HTTP/HTTPS 기반의 애플리케이션이라면 ALB를 사용하는 경우가 많습니다. 

 

ALB와 NLB에 대한 이 두 개의 글이 여러분의 서비스에 적절한 LB를 선택하는데 도움이 되었기를 바랍니다. 긴 글 읽어 주셔서 감사합니다.

반응형