Published on

Terraform apply 후 Azure NSG 규칙이 안 바뀌는 이유

Authors

서버 접근이 갑자기 막히거나(SSH, RDP, HTTPS), 반대로 열어둔 줄 알았는데 여전히 차단되는 상황에서 terraform apply 는 성공으로 끝났는데 Azure Portal의 NSG 규칙이 그대로인 경우가 있습니다. 이 문제는 단순히 “Terraform 버그”라기보다, NSG가 실제로 연결된 지점(Subnet/NI C), 규칙 우선순위, Azure의 비동기 반영, Terraform state 불일치가 겹치면서 발생하는 경우가 많습니다.

이 글에서는 “apply 성공 + NSG 규칙 미반영처럼 보임”을 만드는 대표 원인들을 재현 가능한 체크리스트로 정리하고, Terraform 코드에서 안정적으로 방지하는 패턴까지 다룹니다.

관련해서 네트워크 증상이 애매하게 보일 때는 DNS/외부 통신 관점의 트러블슈팅도 도움이 됩니다: EKS에서 Pod DNS는 되는데 외부 HTTPS만 실패할 때

1) 진짜로 NSG가 “어디에” 붙어 있는지부터 확인

Azure NSG는 크게 두 군데에 연결됩니다.

  • Subnet에 연결(Subnet-level NSG)
  • NIC에 연결(NIC-level NSG)

둘 다 붙을 수 있고, 실제 트래픽은 두 NSG의 규칙을 모두 통과해야 합니다. 즉, Terraform이 Subnet NSG만 바꿨는데 실제로는 NIC NSG가 별도로 붙어 있으면 “규칙이 안 먹는 것처럼” 보입니다.

확인 방법(Azure CLI)

아래 명령으로 NIC에 NSG가 붙어 있는지 확인하세요.

az network nic show \
  -g rg-example \
  -n vm01-nic \
  --query "networkSecurityGroup.id" -o tsv

Subnet에 붙어있는지도 확인합니다.

az network vnet subnet show \
  -g rg-example \
  --vnet-name vnet01 \
  -n subnet01 \
  --query "networkSecurityGroup.id" -o tsv

둘 중 “내가 Terraform으로 관리하는 쪽”이 실제 트래픽 경로에 있는지부터 확정해야 합니다.

2) 규칙은 생겼는데 “우선순위(priority)” 때문에 무시되는 경우

NSG 규칙은 priority 숫자가 낮을수록 먼저 적용됩니다. 흔한 함정은 다음과 같습니다.

  • 내가 만든 Allow 규칙보다 더 낮은 priority(더 강함)의 Deny 규칙이 존재
  • 기본 규칙(Default rules)이나 조직 표준 규칙이 앞에서 막고 있음
  • 여러 규칙이 겹치는데 예상과 다른 매칭이 먼저 발생

체크 포인트

  • 같은 방향(Inbound/Outbound)에서 더 낮은 priority에 Deny가 있는가
  • source/destination 범위가 더 넓은 규칙이 먼저 매칭되는가

Azure CLI로 규칙 정렬해서 보기

az network nsg rule list \
  -g rg-example \
  --nsg-name nsg-web \
  --query "sort_by([].{name:name,priority:priority,access:access,direction:direction,proto:protocol,src:sourceAddressPrefix,dst:destinationAddressPrefix,port:destinationPortRange}, &priority)" \
  -o table

NSG 자체는 변경됐는데도 트래픽이 안 열리면, 이 표에서 Deny가 먼저 잡히는지부터 확인하는 게 가장 빠릅니다.

3) Terraform이 바꾼 NSG가 “그 NSG가 아닌” 경우(리소스 참조 오류)

Terraform 코드에서 NSG 이름이 비슷하거나, 환경별로 리소스가 분리되어 있으면 다음 실수가 자주 납니다.

  • Subnet association이 다른 NSG를 가리킴
  • data 로 가져온 NSG와 resource 로 만든 NSG를 혼용
  • 모듈 변수 전달 실수로 dev NSG를 prod Subnet에 붙이려다 실패 또는 반대로 적용

안전한 패턴: association을 명시적으로 분리

AzureRM에서는 NSG 규칙과 연결을 분리 리소스로 표현할 수 있습니다.

resource "azurerm_network_security_group" "web" {
  name                = "nsg-web"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_subnet_network_security_group_association" "web" {
  subnet_id                 = azurerm_subnet.web.id
  network_security_group_id = azurerm_network_security_group.web.id
}

이렇게 하면 “규칙은 바뀌었는데 실제 연결이 다른 곳” 같은 사고를 줄일 수 있습니다.

4) azurerm_network_security_rule vs 인라인 security_rule 혼용으로 덮어쓰기

NSG 규칙을 정의하는 방식은 크게 두 가지가 있습니다.

  • azurerm_network_security_group 안에 인라인 security_rule 블록으로 정의
  • 별도 리소스 azurerm_network_security_rule 로 정의

두 방식을 같은 NSG에 섞어 쓰면, Terraform이 어느 쪽을 “정답 소스”로 볼지 꼬이면서 apply 때 규칙이 사라지거나 덮어써지는 문제가 생길 수 있습니다(특히 팀 단위로 코드가 분리된 경우).

권장

  • 한 NSG에 대해 한 방식만 사용
  • 모듈화 시에도 규칙 정의 소스를 단일화

예시: 별도 리소스로 통일

resource "azurerm_network_security_rule" "allow_https_in" {
  name                        = "allow-https"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "443"
  source_address_prefix       = "Internet"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.rg.name
  network_security_group_name = azurerm_network_security_group.web.name
}

5) Azure의 비동기 반영과 “포털 캐시”로 인해 안 바뀐 것처럼 보임

terraform apply 가 성공해도 Azure 리소스는 내부적으로 비동기 업데이트가 걸릴 수 있습니다.

  • Portal UI에 늦게 반영
  • 규칙은 반영됐지만 NIC/서브넷 경로에 전파가 지연

검증은 Portal이 아니라 CLI/REST로

Portal에서 안 보인다고 바로 재-apply를 반복하면 오히려 상태가 더 꼬일 수 있습니다.

az network nsg show -g rg-example -n nsg-web --query "securityRules[].name" -o tsv

또는 NSG Flow Log/Connection troubleshoot 같은 “실제 패킷 관점”으로 확인하는 게 정확합니다.

6) state 불일치: apply는 성공했는데 다음 plan이 계속 바뀌는 경우

다음 증상이 있으면 state 또는 외부 변경(drift)을 의심해야 합니다.

  • terraform plan 을 다시 하면 같은 NSG 규칙 변경이 반복적으로 뜸
  • 누군가 Portal/정책/자동화로 NSG를 되돌림

즉시 할 일

  • terraform refresh 는 Terraform 버전에 따라 동작이 다르므로, 보통은 terraform plan 으로 drift를 확인하고 원인을 찾는 것이 우선입니다.
  • Azure Activity Log로 누가/무엇이 변경했는지 확인합니다.

조직에서 정책으로 강제하는 경우(보안팀 표준), Terraform이 매번 바꾸려다 되돌려지는 패턴이 반복됩니다.

7) Policy(또는 Landing Zone)로 NSG 규칙이 자동 수정/차단

Azure Policy, Management Group 레벨의 정책, 또는 Landing Zone 가드레일이 다음을 유발할 수 있습니다.

  • 특정 포트 오픈 규칙 생성 자체가 거부
  • 생성은 되지만 곧바로 Remediation으로 제거
  • 태그/네이밍/우선순위 규칙을 강제

이 경우 Terraform 에러가 나지 않고도 “적용된 듯 보였다가 사라짐”이 생길 수 있습니다(타이밍에 따라).

확인 방법

  • Policy compliance 및 Remediation task 확인
  • Activity Log에서 Microsoft.Authorization/policies 관련 이벤트 확인

8) 규칙은 맞는데 트래픽이 여전히 안 되는: NSG 밖의 원인들

NSG는 네트워크 제어의 일부입니다. 다음이 원인일 수 있습니다.

  • VM OS 방화벽(예: ufw, Windows Firewall)
  • NSG가 아니라 Azure Firewall, NVA, UDR(라우트 테이블)에서 차단
  • Application Security Group(ASG) 타깃이 의도와 다름
  • Public IP가 NIC가 아닌 Load Balancer에 붙어 있고, 실제 백엔드는 다른 NIC

특히 “NSG 규칙은 열었는데 외부 HTTPS만 실패” 같은 증상은, 라우팅/방화벽/프록시까지 같이 봐야 합니다. 네트워크 트러블슈팅 접근은 다음 글의 사고방식도 참고할 만합니다.

9) 실전 체크리스트: 10분 내 원인 좁히기

아래 순서대로 확인하면 대부분의 케이스를 빠르게 좁힐 수 있습니다.

  1. 트래픽 경로의 NSG가 Subnet인지 NIC인지 확인
  2. 해당 NSG의 규칙이 CLI에서 실제로 존재하는지 확인
  3. priority 정렬 후, 더 강한 Deny가 앞에 있는지 확인
  4. 규칙 정의 방식 혼용 여부 확인(인라인 vs 별도 리소스)
  5. Activity Log로 외부 변경/Policy Remediation 여부 확인
  6. NSG 외부 요인(UDR, Firewall, OS 방화벽) 확인

10) 안정적인 Terraform 구성 팁

팁 A: NSG와 association을 분리하고 의존성을 명확히

association 리소스를 분리하면 적용 순서와 참조가 명확해집니다.

resource "azurerm_network_security_group" "app" {
  name                = "nsg-app"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_network_security_rule" "allow_app" {
  name                        = "allow-app"
  priority                    = 210
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "8080"
  source_address_prefix       = "10.0.0.0/8"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.rg.name
  network_security_group_name = azurerm_network_security_group.app.name
}

resource "azurerm_subnet_network_security_group_association" "app" {
  subnet_id                 = azurerm_subnet.app.id
  network_security_group_id = azurerm_network_security_group.app.id
}

팁 B: 규칙 priority는 팀 규약으로 “대역”을 나누기

예를 들어:

  • 100-199: 플랫폼/보안팀 공통 규칙
  • 200-299: 서비스 팀 인바운드
  • 300-399: 운영/점검용 임시(만료 정책 포함)

이렇게 하면 “알 수 없는 Deny가 앞에 있음” 같은 사고를 줄입니다.

팁 C: 변경 검증을 자동화(포털 눈검증 금지)

CI에서 terraform plan 결과만 보지 말고, apply 후에 CLI로 NSG rule 존재/priority를 검증하는 스크립트를 붙이면 재발이 줄어듭니다.

set -euo pipefail

nsg="nsg-web"
rg="rg-example"

az network nsg rule show -g "$rg" --nsg-name "$nsg" -n "allow-https" \
  --query "{name:name,priority:priority,access:access,direction:direction}" -o json

마무리

terraform apply 이후 Azure NSG 규칙이 미반영처럼 보일 때의 핵심은 “Terraform이 뭘 만들었는가”가 아니라, 실제 트래픽이 통과하는 지점에 어떤 NSG가 붙어 있고, 어떤 규칙이 먼저 매칭되는가입니다. 여기에 정책/자동화에 의한 되돌림과 state 불일치까지 겹치면 현상이 더 혼란스러워집니다.

위 체크리스트대로 Subnet/NIC 연결, priority, 규칙 정의 방식 혼용, Policy/Activity Log만 순서대로 확인해도 대부분의 원인을 짧은 시간에 특정할 수 있습니다.

추가로 Azure VM 자체 상태 점검이 필요하면 다음 글도 함께 보면 좋습니다.