- FortiGate-Azure VPN 구성 후 SD-WAN Performance SLA가 정상 작동하도록 만드는 절차, 어떤 .tf 파일을 골라야 하는지 + 각 조합의 함정
- 적용 환경: FortiGate 60F / FortiOS 7.x / Azure Korea vWAN HUB S2S VPN
- GitHub 저장소: https://github.com/20eung/fortios-azure-sla-static
1. Performance SLA가 안 먹는 흔한 이유
FortiGate-Azure VPN을 구성하고 SD-WAN health check를 켰는데, latency가 SLA 임계치 안인데도 SLA가 자주 down됐다 올라왔다 반복하는 현상을 만난 적 있으신가요. tunnel은 up 상태, Azure BGP endpoint도 Established인데 SD-WAN SLA 측정만 비정상입니다.
원인은 거의 항상 세 가지 중 하나입니다.
- SD-WAN member source IP 미지정 — health check가 FortiGate의 default source IP로 나가서 Azure 쪽 응답 경로가 비대칭
- route-map 부재 — BGP에서 in/out 정책 없이 모든 prefix가 그대로 흘러 SD-WAN이 어떤 prefix를 보고 판단할지 애매함
- Static route vs BGP 선택 미스 — SLA Health Check가 정상 측정되려면 Azure peer IP에 대한 정적 경로가 있어야 하는데, BGP만 켜고 정적 경로를 빼먹은 경우
이 글은 위 세 문제를 해결하기 위해 어떤 Terraform 파일을 골라야 하는지, 그리고 각 선택의 결과를 정리합니다. 레포 자체가 선택 가이드 + 검증된 코드 조각 형태로 구성돼 있어 그대로 적용 가능합니다.
💼 post-03(terraform-fortios-bgp-sdwan) 에서 FortiGate-Azure VPN + BGP + SD-WAN Health Check 기본을 다뤘습니다. 이 글은 그 다음 단계로, “구성은 됐는데 SLA가 작동하지 않을 때” 에 집중합니다.
2. 레포의 파일 구조 — 6단계 번호 체계 + 변형 파일
레포의 모든 .tf 파일은 다음 6단계 번호로 구성됩니다. 각 단계별로 여러 변형이 있어 환경에 맞게 선택합니다.
.
├── 01-provider.tf # fortinetdev/fortios provider (필수)
├── 02X-wanN-azvpn-M.tf # WAN1/WAN2 × VPN tunnel 1/2 = 4개 변형
├── 03X-wanN-azvpn-M.tf # 동일 번호 패턴 (Azure 측 tunnel 설정)
├── 04-firewall-policy.tf.{1wan|2wan|sdwan} # 정책 변형 3종
├── 05-route.tf.{static|staticpeerip|bgp|bgp1asn|4neighbors} # 라우팅 변형 5종
└── 06-sdwan.tf.{1wan|2wan|neighbor} # SD-WAN 변형 4종
핵심: 05-route.tf.* 와 06-sdwan.tf.* 의 변형이 환경에 따라 한 가지를 선택해야 합니다. 모두 적용하면 자원이 충돌해 terraform apply가 실패합니다.
💼 파일명의 wan1/wan2 는 WAN 회선 수, bgp1asn/4neighbors 는 BGP neighbor 구성 시나리오를 의미합니다. 테스트 환경은 FG60F에 KT 회선 1개 (WAN1) + SKB 회선 1개 (WAN2) = WAN 2개, Azure vHub는 region 1개에 active-active 인스턴스 2개 = peer 2개, 총 4 tunnel + 4 peer 구조입니다.
3. 환경 정의 — 테스트 베드 수치
원본 레포에서 검증된 테스트 환경은 다음과 같습니다. 환경 수치는 그대로 보존합니다.
| 항목 | 값 |
|---|---|
| FortiGate 모델 | FortiGate 60F |
| WAN1 (KT 회선) | 59.13.29.50 |
| WAN2 (SKB 회선) | 219.241.23.130 |
| Azure region | Korea (vWAN HUB S2S VPN) |
| tunnel IP WAN1 (azvpn-1) | 192.168.101.1/32, remote 10.0.0.4 |
| tunnel IP WAN1 (azvpn-2) | 192.168.101.x, remote 10.0.0.5 |
| tunnel IP WAN2 (azvpn-3, 4) | 192.168.101.21~.22/32 |
| Azure peer 1, 2 | 10.0.0.12, 10.0.0.13 |
| Azure vHub ASN | 65515 |
| 내부 loopback | 192.168.100.0/24, IP 192.168.100.1 |
| Internal 인터페이스명 | TEST-Loopback |
| MSS 사이즈 | tcp_mss=1379 |
| PSK | sknetvpn |
💼 1379 MSS는 Azure Korea region 기준 권장값입니다. 다른 region에서는 1350 또는 1380을 권장하는 문서가 있으니 Azure 공식 문서에서 region별 권장값을 확인하세요.
4. WAN 인터페이스 + IPsec VPN 터널 (02X 단계)
WAN 회선별로 2개 tunnel을 만들어 Azure vHub active-active 인스턴스 양쪽에 연결합니다. WAN1 KT 회선 예시:
# 021-wan1-azvpn-1.tf — WAN1 KT 회선으로 Azure 한국리전 vWAN HUB S2S VPN 터널1 연동
resource "fortios_vpnipsec_phase1interface" "wan1-azvpn-1" {
name = "wan1-azvpn-1"
interface = "wan1"
ike_version = "2"
keylife = 28800
peertype = "any"
net_device = "disable"
proposal = "aes128-sha1"
dhgrp = "2"
remote_gw = "20.47.91.48"
psksecret = "sknetvpn"
dpd = "on-demand"
dpd_retryinterval = "10"
nattraversal = "disable"
}
resource "fortios_vpnipsec_phase2interface" "wan1-azvpn-1" {
name = "wan1-azvpn-1"
phase1name = "wan1-azvpn-1"
proposal = "aes128-sha1"
pfs = "disable"
auto_negotiate = "enable"
keylifeseconds = 3600
depends_on = [fortios_vpnipsec_phase1interface.wan1-azvpn-1]
}
resource "fortios_system_interface" "wan1-azvpn-1" {
name = "wan1-azvpn-1"
vdom = "root"
ip = "192.168.101.1/32"
remote_ip = "10.0.0.4 255.255.255.255"
tcp_mss = "1379"
autogenerated = "auto"
status = "up"
depends_on = [fortios_vpnipsec_phase2interface.wan1-azvpn-1, fortios_vpnipsec_phase1interface.wan1-azvpn-1]
}
022-wan1-azvpn-2.tf, 031-wan2-azvpn-1.tf, 032-wan2-azvpn-2.tf 도 동일 패턴 (remote_gw, IP, 인터페이스명만 다름). WAN 회선 2개 × tunnel 2개 = 4개 .tf 파일이 만들어집니다.
💼 nattraversal=disable이 Azure VPN과의 silent fail 방지 핵심입니다. post-03 3섹션 참고.
5. Firewall Policy (04 단계) — WAN 1개/2개/SD-WAN 3가지 변형
| 파일 | 용도 | 정책 dstintf |
|---|---|---|
04-firewall-policy.tf (SD-WAN) |
SD-WAN 사용 시 기본 | virtual-wan-link |
04-firewall-policy.tf.1wan |
WAN 1개 회선 | 단일 tunnel 인터페이스 |
04-firewall-policy.tf.2wan |
WAN 2개 회선 (zone 분리) | Azure1-VPN + Azure2-VPN |
04-firewall-policy.tf (SD-WAN 사용):
resource "fortios_firewall_policy" "internal-to-virtual-wan-link" {
name = "internal-to-virtual-wan-link"
srcintf { name = "TEST-Loopback" }
dstintf { name = "virtual-wan-link" }
srcaddr { name = "all" }
dstaddr { name = "all" }
action = "accept"
schedule = "always"
service { name = "ALL" }
nat = "disable"
logtraffic = "all"
}
resource "fortios_firewall_policy" "virtual-wan-link-to-internal" {
name = "virtual-wan-link-to-internal"
srcintf { name = "virtual-wan-link" }
dstintf { name = "TEST-Loopback" }
srcaddr { name = "all" }
dstaddr { name = "all" }
action = "accept"
schedule = "always"
service { name = "ALL" }
nat = "disable"
logtraffic = "all"
}
04-firewall-policy.tf.2wan (WAN 2개 + zone 분리 시):
resource "fortios_firewall_policy" "internal-to-Azure1" {
name = "internal-to-Azure1"
srcintf { name = "TEST-Loopback" }
dstintf { name = "Azure1-VPN" }
# ...
depends_on = [fortios_system_interface.wan1-azvpn-1, fortios_system_interface.wan1-azvpn-2]
}
resource "fortios_firewall_policy" "internal-to-Azure2" {
name = "internal-to-Azure2"
srcintf { name = "TEST-Loopback" }
dstintf { name = "Azure2-VPN" }
# ...
depends_on = [fortios_system_interface.wan2-azvpn-3, fortios_system_interface.wan2-azvpn-4]
}
💼 환경에 따라 하나만 선택해서 적용합니다. 모든 변형을 동시에 적용하면 자원이 중복 생성됩니다.
6. Routing (05 단계) — 5가지 변형 중 환경에 맞게 선택
이 단계가 Performance SLA 정상화의 핵심입니다. 어떤 변형을 선택하느냐가 SD-WAN health check 동작을 결정합니다.
| 파일 | 사용 시나리오 | 비고 |
|---|---|---|
05-route.tf.static.tf |
SD-WAN + static route | 단순, BGP 안 쓸 때 |
05-route.tf.staticpeerip |
BGP + Azure peer IP 정적 경로 | BGP neighbor까지 가는 경로 |
05-route.tf.bgp |
BGP 단일 ASN + route-map in/out | route-map으로 SLA 우선순위 |
05-route.tf.bgp1asn |
BGP + 모든 tunnel 단일 ASN (65050) | multi-WAN을 1 ASN으로 통합 |
05-route.tf.4neighbors |
WAN 2개 + tunnel 4개 + peer 4개 | 가장 복잡, multi ASN |
6.1 단순 SD-WAN + static route (05-route.tf.static.tf)
Azure 내부망으로 가는 단일 경로만 필요할 때:
resource "fortios_router_static" "SD-WAN" {
dst = "10.2.0.0 255.255.255.0"
distance = "1"
comment = "Azure Network"
sdwan = "enable"
status = "enable"
depends_on = [fortios_system_sdwan.default]
}
sdwan = "enable" 가 핵심입니다. SD-WAN health check 결과에 따라 우선순위가 자동 조정됩니다.
6.2 BGP peer IP 정적 경로 (05-route.tf.staticpeerip)
BGP session을 Azure와 맺기 전, BGP endpoint까지 가는 경로가 필요합니다.
resource "fortios_router_static" "AzPeer1" {
comment = "Peer1 BGP IP"
dst = "10.0.0.12 255.255.255.255"
distance = "1"
device = "wan1-azvpn-1"
gateway = "10.0.0.4"
status = "enable"
depends_on = [fortios_system_interface.wan1-azvpn-1]
}
resource "fortios_router_static" "AzPeer2" {
comment = "Peer2 BGP IP"
dst = "10.0.0.13 255.255.255.255"
distance = "1"
device = "wan1-azvpn-2"
gateway = "10.0.0.5"
status = "enable"
depends_on = [fortios_system_interface.wan1-azvpn-2]
}
💼 BGP session이 established 되지 않을 때 가장 먼저 확인해야 할 파일입니다. 정적 경로가 없으면 BGP neighbor에 패킷이 닿지 않습니다.
6.3 BGP + route-map (05-route.tf.bgp)
route-map으로 BGP in/out 정책을 분리하고, Performance SLA가 어떤 prefix를 보고 판단할지 명시합니다.
resource "fortios_router_accesslist" "acl-loopback" {
comments = "loopback network"
name = "acl-loopback"
rule {
id = "1"
prefix = "192.168.100.0 255.255.255.0"
exact_match = "enable"
}
}
resource "fortios_router_routemap" "in_sla" {
name = "in_sla"
rule {
action = "permit"
match_ip_address = "acl-loopback"
set_community {
community = "10:1"
}
}
}
resource "fortios_router_routemap" "out_sla" {
name = "out_sla"
rule {
action = "permit"
match_ip_address = "acl-loopback"
set_community {
community = "10:2"
}
}
}
resource "fortios_router_bgp" "bgp" {
as = 65050
router_id = "192.168.100.1"
}
resource "fortios_routerbgp_neighbor" "HubBgpPeer1" {
ip = "10.0.0.12"
route_map_out = "out_sla"
route_map_out_preferable = "in_sla"
update_source = "TEST-Loopback"
}
resource "fortios_routerbgp_neighbor" "HubBgpPeer2" {
ip = "10.0.0.13"
route_map_out = "out_sla"
route_map_out_preferable = "in_sla"
update_source = "TEST-Loopback"
}
route_map_out_preferable = in_sla 가 핵심입니다. FortiOS에서 BGP incoming route에 route-map을 적용해 SLA 우선순위 community 를 붙이고, SD-WAN health check 결과에 따라 그 community를 보고 best path를 선택합니다.
6.4 모든 tunnel을 단일 ASN으로 통합 (05-route.tf.bgp1asn)
multi-WAN 환경에서도 모든 tunnel을 단일 ASN (65050) 으로 운영할 때:
resource "fortios_router_bgp" "bgp" {
as = 65050
router_id = "192.168.100.1"
ebgp_multipath = "enable"
}
resource "fortios_routerbgp_neighbor" "HubBgpPeer1" {
ip = "10.0.0.12"
ebgp_enforce_multihop = "enable"
soft_reconfiguration = "enable"
distribute_list_out = "acl-HubBgpPeer1"
remote_as = "65515"
interface = "wan1-azvpn-1"
}
resource "fortios_routerbgp_neighbor" "HubBgpPeer2" {
ip = "10.0.0.13"
ebgp_enforce_multihop = "enable"
soft_reconfiguration = "enable"
distribute_list_out = "acl-HubBgpPeer2"
remote_as = "65515"
interface = "wan1-azvpn-2"
}
6.5 4개 neighbor 모두 설정 (05-route.tf.4neighbors)
WAN 2개 × tunnel 2개 = 4 tunnel 모두 BGP neighbor 설정 + local_as 로 ASN 분리:
resource "fortios_routerbgp_neighbor" "HubBgpPeer1Asn1" {
ip = "10.0.0.12"
bfd = "enable"
ebgp_enforce_multihop = "enable"
soft_reconfiguration = "enable"
distribute_list_out = "acl-HubBgpPeer1Asn1"
remote_as = "65515"
local_as = "65051"
local_as_replace_as = "enable"
interface = "wan1-azvpn-1"
}
resource "fortios_routerbgp_neighbor" "HubBgpPeer1Asn2" {
ip = "192.168.100.12"
bfd = "enable"
ebgp_enforce_multihop = "enable"
soft_reconfiguration = "enable"
distribute_list_out = "acl-HubBgpPeer1Asn2"
remote_as = "65515"
local_as = "65052"
local_as_replace_as = "enable"
interface = "wan2-azvpn-3"
}
bfd = "enable" 로 sub-second failover 가능, local_as + local_as_replace_as 로 multi-WAN을 ASN별로 분리해 Azure 쪽 라우팅 정책과 정합성 유지.
7. SD-WAN Performance SLA (06 단계) — 4가지 변형
| 파일 | 용도 | 핵심 차이 |
|---|---|---|
06-sdwan.tf |
단일 WAN (4 tunnel 모두 default zone) | health check 단일 |
06-sdwan.tf.1wan |
WAN 1개만 | service 1개 |
06-sdwan.tf.2wan |
WAN 2개 + zone 분리 | service 2개 (Azure1/Azure2) |
06-sdwan.tf.neighbor |
neighbor 설정으로 SLA member별 활성화 | SD-WAN neighbor 블록 사용 |
7.1 기본 SD-WAN (06-sdwan.tf)
4개 tunnel 모두 default zone에 등록, 단일 health check:
resource "fortios_system_sdwan" "default" {
status = "enable"
members {
seq_num = "1"
interface = "wan1-azvpn-1"
gateway = "10.0.0.4"
}
members {
seq_num = "2"
interface = "wan1-azvpn-2"
gateway = "10.0.0.5"
}
members {
seq_num = "3"
interface = "wan2-azvpn-3"
gateway = "10.0.0.4"
}
members {
seq_num = "4"
interface = "wan2-azvpn-4"
gateway = "10.0.0.5"
}
health_check {
name = "sla_azvpn"
server = "10.2.0.132"
members {
seq_num = "0"
}
sla {
id = "1"
link_cost_factor = "latency"
latency_threshold = "100"
}
}
}
latency_threshold=100 ms가 SLA 기준입니다. SLA 측정 서버 10.2.0.132 는 Azure 내부망에 미리 만들어둔 VM입니다.
7.2 WAN 2개 + zone 분리 (06-sdwan.tf.2wan)
resource "fortios_system_sdwan" "default" {
status = "enable"
zone { name = "Azure1-VPN" }
zone { name = "Azure2-VPN" }
members {
seq_num = "1"
interface = "wan1-azvpn-1"
zone = "Azure1-VPN"
gateway = "10.0.0.4"
}
members {
seq_num = "2"
interface = "wan1-azvpn-2"
zone = "Azure1-VPN"
gateway = "10.0.0.5"
}
members {
seq_num = "3"
interface = "wan2-azvpn-3"
zone = "Azure2-VPN"
gateway = "10.0.0.4"
priority = "5"
}
members {
seq_num = "4"
interface = "wan2-azvpn-4"
zone = "Azure2-VPN"
gateway = "10.0.0.5"
priority = "5"
}
health_check {
name = "sla_azvpn"
server = "10.2.0.132"
members { seq_num = "1" }
members { seq_num = "2" }
members { seq_num = "3" }
members { seq_num = "4" }
sla {
id = "1"
latency_threshold = "200"
}
}
service {
id = "1"
name = "Azure1-VPN"
mode = "load-balance"
dst { name = "all" }
src { name = "all" }
sla {
health_check = "sla_azvpn"
id = "1"
}
priority_members { seq_num = "1" }
priority_members { seq_num = "2" }
}
service {
id = "2"
name = "Azure2-VPN"
mode = "load-balance"
dst { name = "all" }
src { name = "all" }
sla {
health_check = "sla_azvpn"
id = "1"
}
priority_members { seq_num = "3" }
priority_members { seq_num = "4" }
}
}
Azure1 zone (WAN1 KT) 과 Azure2 zone (WAN2 SKB) 을 분리하고, 각각 service를 부여해 어떤 zone을 우선으로 쓸지 분리. WAN2 zone은 priority=5로 낮은 우선순위 (백업 회선).
7.3 neighbor 기반 SD-WAN (06-sdwan.tf.neighbor)
Performance SLA가 특정 BGP neighbor에 대해서만 동작하도록 명시:
resource "fortios_system_sdwan" "default" {
status = "enable"
members {
seq_num = "1"
interface = "wan1-azvpn-1"
source = "192.168.100.1"
}
members {
seq_num = "2"
interface = "wan1-azvpn-2"
source = "192.168.100.1"
}
members {
seq_num = "3"
interface = "wan2-azvpn-3"
source = "192.168.100.1"
}
members {
seq_num = "4"
interface = "wan2-azvpn-4"
source = "192.168.100.1"
}
health_check {
name = "sla_azvpn"
server = "10.2.0.132"
members {
seq_num = "0"
}
sla {
id = "1"
link_cost_factor = "latency"
latency_threshold = "100"
}
}
neighbor {
ip = "10.0.0.12"
member = "1"
health_check = "sla_azvpn"
sla_id = "1"
minimum_sla_meet_members = "1"
}
neighbor {
ip = "10.0.0.13"
member = "2"
health_check = "sla_azvpn"
sla_id = "1"
minimum_sla_meet_members = "1"
}
}
source = "192.168.100.1" = health check가 loopback에서 출발하도록 지정. 이게 없으면 FortiGate의 default source IP가 사용되어 Azure 쪽 응답이 비대칭 경로로 들어와 SLA 측정값이 흔들립니다. source 지정은 Performance SLA 정상화의 1순위 조건입니다.
neighbor 블록은 BGP neighbor IP별로 SD-WAN SLA 통과 여부를 추적하도록 만듭니다. Azure VPN active-active 응답 비대칭 문제(post-03 1섹션) 를 정확히 추적할 수 있는 패턴입니다.
8. 어떤 변형을 선택해야 하는가 — 선택 가이드 표
| 환경 | Firewall (04) | Routing (05) | SD-WAN (06) |
|---|---|---|---|
| 단순 1 WAN + SD-WAN | 04-firewall-policy.tf |
05-route.tf.static.tf |
06-sdwan.tf |
| BGP 사용 (단일 ASN, multi-WAN 통합) | .2wan |
05-route.tf.bgp1asn |
06-sdwan.tf.2wan |
| BGP + route-map + Performance SLA 우선순위 | .2wan |
05-route.tf.bgp |
06-sdwan.tf.neighbor |
| WAN 2개 + tunnel 4개 + multi-ASN | .2wan |
05-route.tf.4neighbors |
06-sdwan.tf.neighbor |
💼 “Performance SLA 제대로 작동” 의 핵심 조합은 05-route.tf.bgp (route-map in/out) + 06-sdwan.tf.neighbor (source IP 지정 + neighbor별 SLA) 입니다. 이 조합에서 route_map_out_preferable 로 우선순위 community를 붙이고, SD-WAN neighbor 블록이 BGP neighbor별로 SLA를 추적해 정상 동작합니다.
9. 자주 발생하는 오류와 해결법
| 증상/오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
| tunnel up인데 SLA 측정 안 됨 | source IP 미지정 |
SD-WAN member에 source = "192.168.100.1" (loopback) |
| SLA가 자주 down/up 반복 | health check 응답 비대칭 | 06-sdwan.tf.neighbor 로 BGP neighbor별 SLA 추적 |
| BGP neighbor Established 안 됨 | peer IP 정적 경로 누락 | 05-route.tf.staticpeerip 적용 |
| route-map 적용했으나 SLA 우선순위 미반영 | route_map_out_preferable 누락 |
route_map_out_preferable = "in_sla" 명시 |
| WAN 2개 tunnel 4개 환경에서 일부 tunnel만 active | ASN 충돌 | 05-route.tf.4neighbors 로 local_as 별도 지정 |
| Terraform apply 시 자원 충돌 | 04/05/06 변형 여러 개 동시 적용 | 환경에 맞는 한 가지만 선택해서 적용 |
| health check server 응답 없음 | Azure 내부 VM 미생성 | 10.2.0.132 위치에 SLA 측정용 VM 별도 배포 |
FortiOS 7.x에서 local_as_replace_as 미지원 |
provider 버전 너무 낮음 | fortinetdev/fortios provider ~> 1.20 이상 |
10. 마치며
FortiGate-Azure VPN Performance SLA가 정상 작동하지 않을 때, 대부분은 SD-WAN member source IP 미지정, BGP route-map 누락, Static peer IP 정적 경로 누락 세 가지 원인이었습니다. 이 글의 5가지 라우팅 변형과 4가지 SD-WAN 변형은 이 세 원인을 모두 다룰 수 있도록 설계된 검증된 코드 조각입니다. 환경에 맞는 한 가지만 선택해 적용하면 됩니다. 단, multi-WAN + BGP 환경에서는 06-sdwan.tf.neighbor 의 source 지정 + 05-route.tf.bgp 의 route_map_out_preferable 가 핵심이라는 점을 잊지 마세요.
🎯 핵심 요약
- SD-WAN member
source지정 = loopback IP로 health check 출발점 고정, 응답 비대칭 방지 route_map_out_preferable= incoming route에 우선순위 community 부여, SD-WAN이 best path 선택05-route.tf.staticpeerip= BGP session을 Azure와 맺기 전 필수, peer IP 정적 경로local_as+local_as_replace_as= multi-WAN 환경에서 ASN 분리, Azure 쪽 정책 정합성- 변형은 한 가지만 선택 = 04/05/06 의 모든 변형을 동시에 적용하면 자원 충돌
06-sdwan.tf.neighbor= BGP neighbor별로 SLA 추적, Azure VPN active-active 응답 비대칭 정확히 추적